Ved Thiru

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.


  1. You might be wondering why we don’t stop early if acc > target. I tried that! It didn’t make much of a difference in runtime.
  2. I’m not sure why this function exists.