2022 Day 1: Calorie Counting

[JeremyGrosser][1][Ada]advent/day1_2.adb at 176068a2109faca088baa96b7cb77a078264693c · JeremyGrosser/advent · GitHub
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.

with Advent_IO; use Advent_IO;
with Advent_IO.Integers; use Advent_IO.Integers;
with Ada.Containers.Vectors;

procedure Day1_2 is
   package Natural_Vectors is new Ada.Containers.Vectors
      (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;

[rommudoh][1][Ada] rommudoh/aoc2022-Ada: My Ada solutions for Advent of Code 2022 - aoc2022-Ada - Codeberg.org

[ebriot][1][Ada] advent_of_code/2022/day1 at master · briot/advent_of_code · GitHub

[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…

  with Ada.Text_IO; use Ada.Text_IO;

  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);
     Put_Line ("Answer:" & Max_Calories'Image);
  end Day01_P1;

Here part 2 using an array:

  with Ada.Text_IO; use Ada.Text_IO;

  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);
     Put_Line ("Answer:" & Natural'Image
                 (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.

Day_01.ads

with Ada.Containers.Vectors;

-- Top Level package
package Day_01 is 

   -- Holds the number of calories per item
   type Calories is new Natural;
   
   package Calories_Vectors is new Ada.Containers.Vectors
      (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;

   package Elf_Vectors is new Ada.Containers.Vectors
      (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;

Day_01.adb

with Ada.Text_IO;
with Ada.Strings.Fixed;
with Ada.Exceptions;
with Ada.Command_Line;

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;

      return Total;

   end Total_Calories;

   function Parse_File(Filename : String) return Elf_List is

      File : Ada.Text_IO.File_Type;
      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.
      procedure Add_Existing_Elf is
         use type Ada.Containers.Count_Type;
      begin
         if Elf.Calories.Length > 0 then
            List.Append(Elf);
            Elf.Calories := Calories_Vectors.Empty_Vector;
         end if;
      end Add_Existing_Elf;

   begin

      Ada.Text_IO.Open
         (File => File,
          Name => Filename,
          Mode => Ada.Text_IO.In_File);

      while not Ada.Text_IO.End_Of_File(File) loop
         declare
            Line : constant String
               := Ada.Strings.Fixed.Trim
                  (Source => Ada.Text_IO.Get_Line(File),
                   Side   => Ada.Strings.Both);
            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
               Add_Existing_Elf;
            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
      Add_Existing_Elf;

      Ada.Text_IO.Close(File);
      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 =>
         Ada.Text_IO.Put_Line("Input File Error");
         Ada.Text_IO.Put_Line(Ada.Exceptions.Exception_Information(e));
         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 
            Ada.Command_Line.Argument(1)
          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
   -- the elf had.
   if Elf_With_Max.Valid then
      Ada.Text_IO.Put_Line("Found" & Elf_Count'Image & " elves");
      Ada.Text_IO.Put_Line
         ("Elf #" & Elf_With_Max.Index'Image
          & " has" & Elf_With_Max.Calories'Image
          & " total calories");
   else
      Ada.Text_IO.Put_Line("No Elves Found");
   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. :grinning: 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 :slight_smile:
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 :slight_smile:

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

[AJ-Ianozi][1][Ada]adventofcode/day1.adb at ef78c9c3730a523f3eccaa7993fe2b5f14072393 · AJ-Ianozi/adventofcode · GitHub

with Ada.Containers; use Ada.Containers;
with Ada.Text_IO;    use Ada.Text_IO;
with Ada.Containers.Vectors;

procedure Day1 is
   --  Decided to use a vector for this, since it was the quickest
   package Nat_Vect is new Ada.Containers.Vectors
     (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
https://adventofcode.com/2022/day/1

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). :stuck_out_tongue:

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 :grin:

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.