Advent of Code 2024 - Day1
It’s time once again for Advent of Code. This time I am trying to use it as an opportunity to get better writing Gleam.
Today’s problem involved taking two lists of integers, presented in an input file with one entry from each list separated by three spaces on each line of input, and manipulating these lists to answer a few questions. The first question required we sort the lists and then find the absolute value of the difference between the elements of each list in sorted order. The second part require that we count up the number of occurances of each element from the first list in the second list and sum up the multiple of the value from the first list with the number of occurances in the second list.
My initial solution is shown here. I separated it into three parts. First, the parse_line
function
will transform a line of text from the input file into a list of integers, splitting
on one or more spaces. The second part is to use this function in the
parse_lines
function to create a list of lists of integers, one per input
text file line and then pull out the first element from each sublist to create
the first list, and the second element from each sublist to create the second
list. You can see my first attempt at this function was a little clunky. I
used a separate let statement for each list and went through the combined
list of lists twice, once for each list. In the end I return a tuple containing
the two separate lists.
Finally, in the solve_p1
and solve_p2
functions you can see how I used the
resulting lists to either find the different in the sorted pairs, or to count
the occurances from one list in the next. I went to bed and left refactoring to
the morning.
The changes I made during refactoring can be seen in this code comparison.
I made a new function called solve_p2b
which contains my refactored solution
for counting occurances in the second list. I used a dictionary to count up all
the elements in the second list first, and then iterated through the first list
looking up counts from the dictionary. This is much more efficient. I also first
found it a little difficult to deal with the Result
from using dict.get
to
see if there was an existing entry in the dictionary or if I should just assume
there were 0 entries. One thing I
missed from Go is the idea that maps will always return a
default 0 value, however I quickly found that I could get similar results in
Gleam by using the result.unwrap
function to replace an Error
type with a
literal 0. Effectively this means that if you care if an entry is present in a
map or dictionary in Go you would write
if i, ok := myMap[myKey]; ok {
// The value was found in the map
// ...
} else {
// The value was not found in the map
// ...
}
and in Gleam that would be
case dict.get(my_dict, my_key) {
Ok(i) -> // The value was found and is i ...
Error(Nil) -> // The value was not found ...
}
However, if you just want to assume any value not found is 0, then in Go it is
i := myMap[myKey]
and in Gleam is it as simple as
let i = dict.get(my_dict, my_key) |> result.unwrap(0)
In both cases the language lets you set a default value very concisely, but in the Gleam language it is much more explicitly set.
The other thing I didn’t like was the way I parsed the lines from the input.
I ended up refactoring this quite a bit, which can be seen in the comparison of
lines 57-79 of the initial code to lines 77-86 of the final code.
I had used list.fold
to build the dictionary in the prior refactoring, and I
realized that I could similarly build up the tuple of separate lists from the
combined list using fold as well. In order to do some error checking, namely that
each line had exactly two integers, I used list.try_fold
which would return
an error if it encountered any and I used a case
statement to match on lines
with two integer entries. The resulting parse_lines
function is much more
concise and I believe more readable to someone who is used to functional
programming.
Overall I find I tend to still think very procedurally, which at times leads me to more complex solutions. I hope that as I work through the rest of Advent of Code I can get better at recognizing the best way to perform the data manipulation in a more functional way.