Advent of Code 2024: Day 3
Mix.install([
{:req, "~> 0.5.8"}
])
Data
opts = [headers: [{"cookie", "session=#{System.fetch_env!("AOC_SESSION_COOKIE")}"}]]
input = Req.get!("https://adventofcode.com/2024/day/3/input", opts).body
test_input = "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
part_2_test_input = "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
Part 1
Here we simply use a regex to parse out the operands of mul commands and perform the multiplication and sum as required.
defmodule Part1 do
def go input do
~r/mul\((\d+),(\d+)\)/
|> Regex.scan(input)
|> Enum.map(&to_operands/1)
|> Enum.map(&Enum.product/1)
|> Enum.sum()
end
defp to_operands([_|operands]), do: operands |> Enum.map(&String.to_integer/1)
end
Part1.go test_input
Part1.go input
Part 2
Similar to part 1 but we parse out ful operations with operands. We include the do and don't command so that we know when to stop carrying out multiplications. A more sophisticated version might use NimbleParsec to parse the string and treat don't commands like the start of comment blocks. We use a reduce to go through the operations, keeping track of the do/dont status as we go and calculating the result when appropriate.
defmodule Part2 do
def go input do
Regex.scan(~r/mul\((\d+),(\d+)\)|do\(\)|don't\(\)/, input)
|> Enum.map(&Part2.to_operations/1)
|> Enum.reduce({:do,0},&Part2.process/2)
|> elem(1)
end
def to_operations([_,a,b]), do: { :mul , String.to_integer(a), String.to_integer(b) }
def to_operations(["don't()"]), do: :dont
def to_operations(["do()"]), do: :do
def process(:dont, {_,value}), do: {:dont, value }
def process(:do, {_,value}), do: {:do, value}
def process({:mul, a , b }, { :do, value }), do: {:do, value + a * b }
def process(_, acc), do: acc
end
Part2.go part_2_test_input
Testing
ExUnit.start(auto_run: false, exclude: [:skip])
defmodule Day3Specs do
@test_input test_input
@part_2_test_input part_2_test_input
@real_input input
use ExUnit.Case, async: false
test "part 1 should return x for the test input" do
assert Part1.go(@test_input) === 161
end
test "part 1 should return x for the real input" do
assert Part1.go(@real_input) === 173731097
end
test "part 2 should return x for the test input" do
assert Part2.go(@part_2_test_input) === 48
end
@tag :skip
test "part 2 should return x for the real input" do
assert Part2.go(@real_input) === 93729253
end
end
ExUnit.run()
Results
Part 1 Result
Part1.go(input)
Part 2 Result
Part2.go(input)