Advent of Code Day 7, in Elixir!
published 2024-12-07
concurrent brute force again, with fancy pattern matching!
I did Day 7 in Elixir! (Previously: Day 6, in Elixir, and Day 1, Day 2, Day 3, Day 4, Day 5, all in Raku).
Today’s solution is pretty straightforward. Again, part 2 is similar to part 1 so I’ll just cover part 2.
defmodule BridgeCalibrations do
defp test(target, target, []), do: true
defp test(_target, _acc, []), do: false
defp test(target, acc, [i | rest]) do
test(target, acc + i, rest) or test(target, acc * i, rest)
or test(target, concat(acc, i), rest)
end
def test(target, [i | rest]), do: test(target, i, rest)
defp concat(a, b), do: a * 10 ** (b |> Integer.digits |> Enum.count) + b
end
test
pattern matches on its inputs. If we’ve reached the target and there are no values remaining, the line succeeds. Otherwise, if they’re different, the line fails the test.1 If the array isn’t empty, we test adding, multiplying, and concatenating.
concat
uses the Integer.digits
function, which returns a list of digits in the number.2
Now to use the module:
File.stream!("data.txt")
|> Task.async_stream(fn line ->
[target, operands] = String.split(line, ": ")
operands = operands |> String.trim |> String.split(" ") |> Enum.map(&String.to_integer/1)
target = String.to_integer(target)
if BridgeCalibrations.test(target, operands), do: target, else: 0
end, ordered: false)
|> Stream.map(fn {:ok, i} -> i end)
|> Enum.sum
|> IO.inspect
We spawn a task for each line that parses the line, then tests. Then we sum up the results. Spawning a task for each line with Task.async_stream
cuts our runtime by 200ms
on my machine compared to a Stream.map
, which is pretty significant as our overall runtime (using tasks) is 400ms
; that’s a 33% reduction! BEAM processes are pretty lightweight.