2024 Day 2: Red-Nosed Reports

Tripped myself up by being too clever… My Advent library doesn’t support parsing integers like it did in previous years, so I wasted a few minutes implementing that. Could’ve just used Integer_IO if I weren’t so stubborn :slight_smile:

Changed my mind about using a Vector halfway through and switched to an indefinite array. Made some things a little easier, but part 2 would’ve been simpler had I just stuck to Vectors. Oh well.

I tried to do part 2 by adjusting my comparison counts in Is_Safe, but that got messy and I started having off-by-one bugs… A clear sign that I was on the wrong track. Ended up going with the brute force solution and called Is_Safe with each missing level. It works, I guess.

advent/2024/src/day2_2.adb at 8571ee23485b41d452494300974a367e6e980a05 · JeremyGrosser/advent · GitHub

I also tried to be clever and fast, but didn’t manage both, so went for fast using brute force, to finish before leaving for work. :smile:

I might try a more clever version later today, though; would be a good opportunity to also check out spark_unbound. So, yeah, at least I learned that vectors are not allowed in SPARK. Not surprising, but I guess every little insight counts…

Done, but I spent almost more time just parsing the report lines to get the levels than on the logic for finding if they’re safe. Is there a simple way to split up a string into separate components using a character separator?

There’s indeed a lot of work parsing the file, but … now I realized one could also do it like I do it for the JavaScript that I use to verify my solution: just paste the contents in a large matrix, and forget about text io.

BTW, today I got to use one of the Ada constructs I liked from the moment I saw them: qualified expressions. For part 2, I’m using

if (for some Excl in 0 .. N - 1 => Report_Minus_1_Is_Safe (Report, N, Excl))

That’s so readable!

2 Likes

There might be better ways, but I leverage the Index function from Ada.Strings.Fixed:

with Ada.Strings.Fixed;
with Ada.Containers.Indefinite_Vectors;
with Ada.Text_IO;

procedure Example is
   package String_Vectors is new Ada.Containers.Indefinite_Vectors
      (Index_Type   => Positive,
       Element_Type => String);

   function Split(Item : String; Separator : String) return String_Vectors.Vector;

   function Split(Item : String; Separator : String) return String_Vectors.Vector is
      First : Natural := Item'First;
      Last  : Natural := Item'Last;
   begin
      return Result : String_Vectors.Vector do
         loop
            Last := Ada.Strings.Fixed.Index(Item(First..Item'Last), Separator);
            exit when Last = 0;
            Result.Append(Item(First .. Last-1));
            First := Last + Separator'Length;
         end loop;
         Result.Append(Item(First .. Item'Last));
      end return;
   end Split;

   v1 : String_Vectors.Vector := Split("a b c"," ");
begin
   for Item of v1 loop
      Ada.Text_IO.Put_Line("Substring: " & Item);
   end loop;
end Example;

That’s a very over simplified logic with very little error checking, so it generates a vector element even if the string is empty or there are two back to back separators (but the added element is a null string, so that is checkable), but that can be adjusted by the programmer.

1 Like

For splitting your string, you can use the csv package

and use ’ ’ as a separator.

However, another, IMHO much simpler, solution is to use Get (file, i) from Ada.Text_IO.Integer_IO.
You know that you are at the end of a line with a call to End_Of_Line (file).
No need to absorb the end-of-line character(s), nor the spaces, it’s done for you.

You can see my AoC solution for the details (link below).
Done with HAC, but HAC’s toolbox just calls Ada.Text_IO.* .
Basically the parser looks like this:

    while not End_Of_File (f) loop
      last := last + 1;
      Get (f, data (last));
      if End_Of_Line (f) or End_Of_File (f) then
        --  ... Do something with the complete line
        last := 0;
      end if;
    end loop;

The or End_Of_File (f) is there for the case the last line doesn’t end with an end-of-line.

2 Likes

[fredpraca][02][Ada]Red-Nosed Reports Solution

So far, so good :slight_smile:
Clearly not the best solution out of there but it works and it’s finally quite simple for my point of view.
I’ll check the other solutions later.

So much nicer than what I ended up doing:

if
   (for some N in Numbers'Range =>
      Is_Safe --  Pass a dynamically-created array into is_safe
         ((if N = Numbers'First then
             Numbers (N + 1 .. Numbers'Last)
           elsif N = Numbers'Last then
             Numbers (Numbers'First .. N - 1)
           else
             Numbers (Numbers'First .. N - 1) &
             Numbers (N + 1 .. Numbers'Last))))
then
   Result_P2 := @ + 1;
end if;
1 Like

Huh… you didn’t use this trick?

Function Parse(S : String) return Integer is
Begin
  Return Integer'Value( S );
End Parse;

I did, but I usually prefer to hand roll the ascii-to-integer conversion. I’ve noticed on embedded platforms that GNAT’s ‘Value implementations are often extremely slow and don’t get vectorized/inlined properly.

1 Like