2024 Day 1: Historian Hysteria

I went with Vectors for the first bit, and an Ordered_Map for the second part. I’d forgotten how to use Generic_Sorting, so that took me a few minutes to read through the RM, but not too bad.

Definitely not an optimal solution, curious to see if anybody did it with arrays or trees.

advent/2024/src/day1_1.adb at 261d2fd25cf21b545c60fc69c77869b2f522a8fb · JeremyGrosser/advent · GitHub
advent/2024/src/day1_2.adb at 261d2fd25cf21b545c60fc69c77869b2f522a8fb · JeremyGrosser/advent · GitHub

Used the means available with HAC (that is, not a lot…):

1 Like

I repackaged Ada 83 frontend into alire crate and processed my day 1 solution with it. I was wondering how functional it is. Maybe it makes sense to make a toy Ada 83 compiler based on it. I couldn’t get through Vincent’s code. :clown_face:

1 Like

I added my opinion in the main thread, but I think this thread will be better:

First impression… This is the first time I have used Vectors. I did not know that for the input type definition one has to do Instantiated_Vector_Package_Name.Vector and not just Instantiated_Vector_Package_Name. It makes sense after all, but the compiler was not useful at all! I kept saying subtype mark error vector but I just did not understand it…
Additionally, iterating over two vectors with the same index was not as clean as I had hoped… I wanted to do a for Val_1 of Vec_1 and for Val_1 of Vec_1 but that does not work so cleanly… So direct indexing for I in Vec_1.First_Index..Vec_1.Last_Index was the way to go. And this also shows the issue of tagged types vs normal types IMHO… One cannot do a simple Vec_1'Range…

My solution is quite dirty too, but it works. I was very happy to gain the gold star after compiling it once and running it :slight_smile: That is the power of Ada (and it was also a simple problem too :stuck_out_tongue: ) Though it could have been greatly improved if I made use of the fact that the vectors are sorted…

Overall, it took me less that I expected, more than it should have :stuck_out_tongue:

Best,
Fer

Solution: advent-of-code-2024/day_1 at main · Irvise/advent-of-code-2024 · GitHub

My small contribution :slight_smile:

Used two vectors and generic vector sorting. The trickiest bit was indexing both vectors at the same time, but it wasn’t too hard :slight_smile:

[alexispaez][01][Ada]advent-of-code/2024/day_1_1/src/day_1_1.adb at 701d5f09cdbaa53a54003172e1a647b244136e61 · alexispaez/advent-of-code · GitHub

[alexispaez][01][Ada]advent-of-code/2024/day_1_2/src/day_1_2.adb at master · alexispaez/advent-of-code · GitHub

Added part 2 as well.

1 Like

Solved it in a very similar way as everyone else, except I took advantage of the Ada 2022 feature of filtering for loops to count the vector:

function Count (List : ID_List.Vector; Item : Integer)
   return Integer
is
   Result : Integer := 0;
begin
   for I of List when I = Item loop -- Using "when" so no if
      Result := @ + 1;
   end loop;
   return Result;
end Count;

Here’s the github link: [aj-ianozi][01][Ada]

3 Likes

I used Cursors for that. Fairly clean, hopefully idiomatic, but not as clean as e.g. a Zip() function that combines to vectors into one. I’m an Ada novice, so I wouldn’t know how to write it, though.

1 Like

[fredpraca][01][Ada]My solution

I didn’t watch the other solutions but I’m quite sure it’s very similar to evyone else solution :slight_smile:
I haven’t code in Ada for so long that it took time to do it but it finally works. Good to be back again in Ada programming :smiley:

4 Likes

Right, coming from other languages, I’m really missing basic itertools-like functionality in Ada. Even worse that you can’t (easily?) write such a general library yourself because of the way generics are working.

function Solve_Part_1 return Natural is
    use Sorting;
    Total : Natural := 0;
begin
    Sort (Left);
    Sort (Right);
    for I in Left.First_Index .. Left.Last_Index loop
        Total := @ + abs (Left (I) - Right (I));
    end loop;
    return Total;
end Solve_Part_1;

feels so inelegant compared to

sum(abs(id1 - id2) for (id1, id2) in zip(left, right))
-- or even
zip(left, right).map!((id1, id2) => abs(id1 - id2)).sum;

My solution.

Initially I coded it using constrained arrays (knowing the input size), then switched to over Containers.Vectors. In part 2 I tried to take advantage of the fact that the inputs were already sorted, processing the inputs in a single pass.

I’m pretty new to Ada, so spent most of my time of time searching the RM and web rather than writing the code.

Wouldn’t this fail if left or right isn’t sorted? Or does zip() also presort the inputs? I’m unfamiliar with zip(), so was curious how it worked.

EDIT: One additional way is to use the Reduce attribute:

Sort (Left);
Sort (Right);
Sum := [for Index in 1 .. Left.Last_Index => abs(Left(Index)-Right(Index))]
         'Reduce("+",0);

I put the 'Reduce on the following line to highlight it. It normally goes on the same line, but up to the programmer of course.

3 Likes

Yes, the two examples are incomplete and would fail when executed like that. The actual code is sorting the lists, just like in Ada.

zip takes n iterators or iterable objects as input and returns a new iterator that continuously yields tuples of n elements. And it does that till one of the original iterators runs dry. Here’s julia’s version, for example. Some languages also have the inverse unzip function that transforms the resulting single iterator back into multiple lists/iterators again.

Good point regarding 'Reduce and I think I tried that, but got some weird “left something needs to have a name” error and simply gave up since it was already way too late at my end. :slightly_smiling_face:

I hadn’t come across 'Reduce yet. Nice!

I’m not sure if it has zip functionality, and I haven’t tested it much myself (if at all – can’t recall), but Alejandro Mosteo’s iterators library (available in Alire) looks as if it has at least some of what you’re looking for.

1 Like

Thanks for the link. While it’s impressive what he managed to build despite Ada’s rigid generics, you end up with code like this, straight from the readme:

 package Ints is new Iterators.From.Elements (Integer);
 package Strs is new Iterators.From.Elements (String);
 package Int2Str is new Iterators.Operators (Ints.Iterators, 
                                             Strs.Iterators);

 use Ints.Linking, Strs.Linking, Int2Str.Linking; -- Make "&" visible

 Result : constant Ints.Iterators.List :=
            Ints.Iter (Some_Collection_Of_Integers)
            & Ints.Op.Filter (Some_Condition'Access)
            & Int2Str.Op.Map (Some_Integer_String_Transform'Access)
            & Strs.Op.Collect;

opposed to:

fn main() {
    let result: Vec<String> = [-3_i8, 0, 1, 3].iter()
        .filter(|x| x.is_positive())
        .map(|x| x.to_string())
        .collect();
}

And you have to introduce more and more line noise and overhead for every new type or transformation. Not even considering your own data structures aside from Ada.Containers.

AdaCore and the ARG are currently cooking and I’m looking forward to anonymous subprograms and the generic improvements proposed in the various AIs.

2 Likes

Nice. Thanks for the link. I’m curious to see how it was done. I stumbled getting all the types and functions that make up an iterator aligned, and it’s hard to find concrete examples. Well, I’m a noob anyway.