# 2022 Day 1: Calorie Counting

Kind of a rough start for me this year, I pulled in my Stream I/O library and test framework from last year, but I think that just made things more complicated than they need to be.

For part 2, I took the naive approach, inserting all of the totals into a Vector, sorted, reversed, then summed the top 3 elements. The Reverse_Elements is expensive and could be avoided, but I think counting from 1 … 3 makes the code easier to read.

procedure Day1_2 is
(Index_Type   => Positive,
Element_Type => Natural);
use Natural_Vectors;

package Natural_Sorting is new Natural_Vectors.Generic_Sorting;
use Natural_Sorting;

V : Vector := Empty_Vector;
Total : Natural;
begin
while not End_Of_Input loop
declare
Line : constant String := Read_Until (Input, CRLF);
begin
if Line'Length = 0 then
Append (V, Total);
Total := 0;
else
Total := Total + Natural'Value (Line);
end if;
end;
end loop;

Append (V, Total);

Sort (V);
Reverse_Elements (V);

Total := V (1) + V (2) + V (3);

Put (Output, Total);
New_Line (Output);
end Day1_2;

[zetah11][1][SPARK][GitHub - zetah11/aoc2022: Some of my Advent of Code 2022 solutions]

[rocher][1][Ada] My solution for part 1. Quite simple, it’s just the first day…

procedure Day01_P1 is

Input        : File_Type;
Calories     : Natural := 0;
Max_Calories : Natural := 0;

function Get_Max_Calories (K: Natural) return Natural is
(if K > Max_Calories then K else Max_Calories);

begin
Open (Input, In_File, "input.txt");

loop
declare
Line : String := Get_Line (Input);
begin
if Line'Length = 0  then
Max_Calories := Get_Max_Calories (Calories);
Calories := 0;
else
Calories := @ + Natural'Value (Line);
end if;
end;
exit when End_Of_File (Input);
end loop;
Max_Calories := Get_Max_Calories (Calories);

Close (Input);
end Day01_P1;

Here part 2 using an array:

procedure Day01_P2 is

Input        : File_Type;
Calories     : Natural := 0;
Max_Calories : array (1 .. 3) of Natural := (0, 0, 0);

procedure Get_Max_Calories is
begin
for I in Max_Calories'Range loop
if Calories > Max_Calories (I) then
Max_Calories (I+1 .. Max_Calories'Last) := Max_Calories (I .. Max_Calories'Last-1);
Max_Calories (I) := Calories;
exit; -- this  loop
end if;
end loop;
end Get_Max_Calories;

begin
Open (Input, In_File, "input.txt");

loop
declare
Line : String := Get_Line (Input);
begin
if Line'Length = 0 then
Get_Max_Calories;
Calories := 0;
else
Calories := @ + Natural'Value (Line);
end if;
end;
exit when End_Of_File (Input);
end loop;
Get_Max_Calories;

Close (Input);
(Max_Calories (1) + Max_Calories (2) + Max_Calories (3)));
end Day01_P2;

I decided to have fun with it and layout a design first. I also wanted to use some various features from Ada, so I embellished a bit on the code. I didn’t generate a github repo yet for it and I may not do all days. Not sure yet.

-- Top Level package
package Day_01 is

-- Holds the number of calories per item
type Calories is new Natural;

(Index_Type   => Positive,
Element_Type => Calories);

-- Holds info about each elf
type Elf is record
Calories : Calories_Vectors.Vector;
end record;

-- Returns the total calorie consumption for the elf
function Total_Calories(Self : Elf) return Calories;

(Index_Type  => Positive,
Element_Type => Elf);

-- A list of elves
subtype Elf_List is Elf_Vectors.Vector;

-- Open a file and generate a list of elves with calories
function Parse_File(Filename : String) return Elf_List;

-- This holds the results of an Elf_List search for who
-- has the most calories.  If Valid = False, then no
-- elves were in the list
type Search_Result(Valid : Boolean := False) is record
case Valid is
when False => null;
when True  =>
Index    : Positive := 1;
Calories : Day_01.Calories := 0;
end case;
end record;

-- Searches through an elf list for the one with the
-- most calories;
function Most_Calories(List : Elf_List) return Search_Result;

end Day_01;

package body Day_01 is

function Total_Calories(Self : Elf) return Calories is
Total : Calories := 0;
begin

for Calories of Self.Calories loop
Total := @ + Calories;
end loop;

end Total_Calories;

function Parse_File(Filename : String) return Elf_List is

List : Elf_List;
Elf  : Day_01.Elf;

-- Attemps to add an elf to the list if
-- one has data associated with it.  Once
-- an elf is added, the Elf variable's
-- data is reset for the next one.
begin
if Elf.Calories.Length > 0 then
List.Append(Elf);
Elf.Calories := Calories_Vectors.Empty_Vector;
end if;

begin

(File => File,
Name => Filename,

declare
Line : constant String
Calories : Day_01.Calories := 0;
begin
if Line'Length = 0 then
-- If we received a blank line, then try to
-- add any existing elf to the list
else
-- Format the string to a just a number
-- using the Value attribute of the
-- Calories type
Calories := Day_01.Calories'Value(Line);
Elf.Calories.Append(Calories);
end if;
end;

end loop;

-- Get last elf if there is one

return List;

exception
-- If an exception was raised, it means there is an
-- input file error.  Print out some debug and return
-- an empty list
when e : others =>
return Elf_Vectors.Empty_Vector;
end Parse_File;

function Most_Calories(List : Elf_List) return Search_Result is
Calories     : Day_01.Calories := 0;
Max_Calories : Day_01.Calories := 0;
Elf_With_Max : Positive        := 1;
Index        : Positive        := 1;
begin
for Elf of List loop
Calories := Total_Calories(Elf);
if Calories > Max_Calories then
Elf_With_Max := Index;
Max_Calories := Calories;
end if;

-- This check is for the unrealistic situation
-- that the vector has Positive'Last number of
-- elements
exit when Index = Positive'Last;
Index := @ + 1;
end loop;

-- If the loop was entered at all, Index will increment.
-- This is how we determine at least one elf was found
-- in the list.
if Index > 1 then
return
(Valid    => True,
Index    => Elf_With_Max,
Calories => Max_Calories);
else
return (Valid => False);
end if;

end Most_Calories;

-- Program variables
Filename     : constant String
:= (if Ada.Command_Line.Argument_Count > 0 then
else
"input.txt");
List         : constant Elf_List := Parse_File(Filename);
Elf_Count    : constant Ada.Containers.Count_Type := List.Length;
Elf_With_Max : constant Search_Result := Most_Calories(List);

begin

-- If an elf was found, print it along with the total calories
if Elf_With_Max.Valid then
Ada.Text_IO.Put_Line("Found" & Elf_Count'Image & " elves");
("Elf #" & Elf_With_Max.Index'Image
& " has" & Elf_With_Max.Calories'Image
& " total calories");
else
end if;

end Day_01;

I just withed that in an bare empty main adb file.

1 Like

I’ve tried 'Reduce from Ada 2022 and found a compiler bug. Also I’ve spent time with SPARK. This my very first experience with SPARK and it seems it works for day 1.

PS. Looking for others SPARK solution I found a powerful statement:

--  Trust me, I'm an engineer...
pragma SPARK_Mode (Off);
2 Likes

Here is my solution with HAC: hac/aoc_2022_01.adb at dfa2b7d2125e4324d6898d3d00e424478ee413bf · zertovitch/hac · GitHub

As usual, it runs on both HAC (command line: hac aoc_2022_01.adb; LEA: press F9 key), and a “full Ada” compiler. For GNAT, you can use the aoc_2022.gpr project file.

Hello there
First time doing the AoC, here’s my solution (on compiler-explorer): part 1 and part 2
Had very little time so went for the most naive solution I could come up with

1 Like

If anyone’s interested in benchmarking their code, I wrote a Python script to generate an input file with 10M elves. My best solution so far solves it in 12.216 seconds (user time, measured by perf stat) on an i7-8700.

The correct solution for this input is 227000

procedure Day1 is
--  Decided to use a vector for this, since it was the quickest
(Index_Type => Natural, Element_Type => Natural);
package Nat_Sort is new Nat_Vect.Generic_Sorting;
use Nat_Vect, Nat_Sort;

My_File : File_Type;
Sums    : Vector;
Sum     : Natural := 0;
begin
Open (My_File, In_File, "input/Day1-1.txt");
while not End_Of_File (My_File) loop
declare
Next_Line : constant String := Get_Line (My_File);
begin
if Next_Line = "" then
--  Add this sum to our vector.
Sums.Append (Sum);
--  Reset sum
Sum := 0;
else
--  Continue to accumulate Sum
Sum := Sum + Natural'Value (Next_Line);
end if;
end;
end loop;
--  Add whatever is left.  TODO: Stop doing this?
Sums.Append (Sum);
--  Now sort our vector
Sort (Sums);
Put_Line
("The highest calory elf has " & Natural'Image (Sums.Last_Element) &
" calories.");
--  Now all we have to do is sum the top 3.
Sum := 0;
for I in --  This gets our top 3 picks, since it's been sorted.
(if Sums.Length > 2 then Sums.Last_Index - 2 else Sums.First_Index) ..
Sums.Last_Index
loop
Sum := Sum + Sums (I);
end loop;
Put_Line ("Top 3 elf calories combined are: " & Sum'Image);
end Day1;

Great minds think alike I suppose; seems like most people did a very similar solution, with sorted vectors.

I’m really looking forward to how the more complicated ones get solved.

So I only see the question for part 1. Where is part 2 coming from. For reference, I am looking at

But it doesn’t mention summing the top 3.

Once you’ve submitted a correct solution for part 1 using the text box at the bottom, the part 2 problem is revealed.

I tried with SPARK, but quickly ran into some errors because it wasn’t easy to modify my Ada solution. I appreciate your solution to this!

1 Like

I have 4,343 seconds on an i7-7700. (And 3,827 in case of alr build --release).

1 Like

Corner Test Cases.

Only for curiosity, what’s the answer you get with your implementation of day 1/part 1 when the input file is this?

111

999

And this?

999

I get 999 as the answer for both. What were you expecting?

I guess your test is for the following trap: if you test the maxima after each separator but not after the end of the file, you get wrong answers (111 on input #1 and the initial value for the maximum on input #2).

For the fun of it: your jumbo data generator with HAC

2 Likes

It did feel a bit sacrilegious writing it in Python, but a lot of people want to play with jumbo inputs that aren’t using Ada and don’t have a compiler for it. I figure nearly every machine has a Python interpreter available.