Advent of Code 2022 Day 3
The language
Elixir .. oh boy.
Installation is straight forward on debian, just apt-get install elixir
.
REPL is then available with iex
and compiler with elixir $script_file
.
Looks really nice, but functional programming is not something I am good at and
therefore this took quite a while…
The task
First part: Get a list of strings, split them in half, search for common chars in both halfs, sum up points for each unique character which fits into that profile. Second part: Take three lines together and search for common characters as above minus the splitting.
The code
I really do hope no elixir programmer sees this code. If you are an elixir programmer (or know any functional language better than me), you have hereby been warned.
defmodule Day3 do
Start with a module declaration. I think I had to do that.
foo = Day3.solve()
IO.inspect(foo)
This is actuall at the very and just actually calls the code and prints the result.
I used IO.inspect
here, because IO.puts
complained something about the data type or something
like that.
def solve() do
input = readInput()
|> Enum.map(fn x -> String.to_charlist(x) end)
[{:part1, part1(input)}, {:part2, solvePart2(input)}]
end
Main function of the module, reads the input, transforms the string in to a charlist
and the
calls the individual solvers.
Input looks like this:
def readInput() do
tmp = IO.read(:stdio, :line)
case tmp do
:eof -> [ "" ]
_ -> [ String.trim(tmp, "\n") ] ++ Day3.readInput()
end
end
Basically “Read a line, remove the ‘\n’ and concat the result of a recursive call until EOF
”.
The case
statement works mostly like switch
in other languages, it is only confusing, because
it works with patter matching (which binds variables).
def part1(val) do
#|> IO.inspect(label: "Input")
#|> IO.inspect(label: "Charlists")
val
|> Enum.map(fn x -> Day3.splitEntry(x) end)
#|> IO.inspect(label: "Splitted")
|> Enum.map(fn x -> Day3.findCommons(x) end)
#|> IO.inspect(label: "Commons")
|> List.foldl(0, fn x, acc -> if x != [] do Day3.getValue(hd(x)) + acc else acc end end)
end
The #
are comments. |>
in the next line means we want to “pipe” that value into the next
statement, really nice to use actually.
It splits the lines in half (splitEntry/1
), finds the common chars (findCommons/1
) and then
sums up the values (retrieved from each char with getValue/1
) wie a left fold function.
def splitEntry(a) do
case a do
[] -> {[], []}
[x] -> {[x], []}
[x|y] -> Day3.splitEntryInternal([x], y)
end
end
def splitEntryInternal(a, b) do
if length(a) < length(b) do
splitEntryInternal([ hd(b) | a ], tl(b))
else
{a, b}
end
end
splitEntry/1
is the entry function here, and calls the internal one then.
This will recursively call itself and move characters from b
to a
until
both strings have equal length (or technically: a
is no longer shorter than b
).
It took me a while to find the slice functionality, but then I did not want to touch this
anymore.
def findCommons(val) do
Day3.findCommonsInternal(val, [])
end
def findCommonsInternal(val, present) do
case val do
{_, []} -> []
{[], _} -> []
{[w | x], y } ->
case w in y do
true ->
if !Enum.member?(present, w) do
present = present ++ [w]
Day3.findCommonsInternal({x, y}, present) ++ [w]
else
Day3.findCommonsInternal({x, y}, present)
end
false -> Day3.findCommonsInternal({x, y}, present)
end
end
end
Once again, go recursively through the first list (I packed them into tuples, for some reason, that’s
why there is only one parameter). The internal functions also has a present
list for remembering
characters which where already found to be present in both parts, so as to not count them multiple
times.
Now to part 2:
def solvePart2(list) do
case list do
[a, b, c | rest ] ->
tmp = getValue(Day3.findCommons(a, b, c))
tmp2 = Day3.solvePart2(rest)
if tmp2 == [] do
tmp
else
tmp + tmp2
end
_ -> []
end
end
def findCommons(a, b, c) do
foo = findCommons({a, b})
foo = findCommons({foo, c})
hd(foo)
end
For this I introduced findCommons/3
which takes three strings and finds the common chars, by
applying the previous functionality to first two and then the result to the third.
Conclusion
It was quite a fight to get elixir to do what I intented, but I guess I could learn to like it
given some time. The “pipe” syntax and IO.inspect
were quite nice to use.
Functional programming is hard, if you never do it.