diff --git a/README.md b/README.md index 935209f..3648048 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ My Elixir solutions for [Advent of Code](https://adventofcode.com/) (all years). -
- +
diff --git a/lib/y2024/README.md b/lib/y2024/README.md
index 766d5fe..eac4a5b 100644
--- a/lib/y2024/README.md
+++ b/lib/y2024/README.md
@@ -2,7 +2,7 @@
My Elixir solutions for [Advent of Code 2024](https://adventofcode.com/2024).
-
+
## Benchmarks
@@ -38,5 +38,6 @@ day 11, part 1 838.21 1.19 ms ±4.86% 1.21 ms 1.
day 11, part 2 19.52 51.22 ms ±1.86% 51.26 ms 53.37 ms
day 12, part 1 7.16 139.70 ms ±1.82% 139.22 ms 144.95 ms
day 12, part 2 6.84 146.22 ms ±1.32% 145.82 ms 151.25 ms
-day 13, part 1 4.91 203.63 ms ±0.46% 203.49 ms 206.75 ms
+day 13, part 1 1.31 K 765.25 μs ±3.72% 754 μs 837.77 μs
+day 13, part 2 1.33 K 753.73 μs ±3.42% 744.42 μs 822.51 μs
```
diff --git a/lib/y2024/day13.ex b/lib/y2024/day13.ex
index 7df8a0f..503df24 100644
--- a/lib/y2024/day13.ex
+++ b/lib/y2024/day13.ex
@@ -2,7 +2,10 @@ defmodule Y2024.Day13 do
use Advent.Day, no: 13
@doc """
- iex> Day13.part1([%{buttons: %{a: %{x: 94, y: 34, cost: 3}, b: %{x: 22, y: 67, cost: 1}}, prize: %{x: 8400, y: 5400}}])
+ iex> Day13.part1([
+ ...> %{buttons: %{a: %{x: 94, y: 34, cost: 3}, b: %{x: 22, y: 67, cost: 1}},
+ ...> prize: %{x: 8400, y: 5400}}
+ ...> ])
280
"""
def part1(machines) do
@@ -14,51 +17,63 @@ defmodule Y2024.Day13 do
end)
end
- # @doc """
- # iex> Day13.part2("update or delete me")
- # "update or delete me"
- # """
- # def part2(input) do
- # input
- # end
+ @doc """
+ # iex> Day13.part2([
+ # ...> %{buttons: %{a: %{x: 26, y: 66, cost: 3}, b: %{x: 67, y: 21, cost: 1}},
+ # ...> prize: %{x: 10000000012748, y: 10000000012176}}
+ # ...> ])
+ # 459236326669
+ """
+ def part2(machines) do
+ machines
+ |> Enum.map(&find_solution/1)
+ |> Enum.reduce(0, fn
+ %{a: a, b: b}, acc -> acc + a.tokens + b.tokens
+ nil, acc -> acc
+ end)
+ end
@doc """
iex> Day13.find_solution(%{buttons: %{a: %{x: 94, y: 34, cost: 3}, b: %{x: 22, y: 67, cost: 1}}, prize: %{x: 8400, y: 5400}})
%{a: %{presses: 80, tokens: 240}, b: %{presses: 40, tokens: 40}}
"""
- def find_solution(%{buttons: %{a: a, b: b}, prize: prize}) do
- a_presses = Enum.min([100, div(prize.x, a.x), div(prize.y, a.y)])
- b_presses = Enum.min([100, div(prize.x, b.x), div(prize.y, b.y)])
+ def find_solution(%{buttons: %{a: button_a, b: button_b}, prize: prize}) do
+ # There are two simultaneous equations that need solving = flip the
+ # button formulas we have to solve for a and b.
+ a = %{one: button_a.x * button_b.cost, two: button_a.y * button_b.cost}
+ b = %{one: button_b.x * button_a.cost, two: button_b.y * button_a.cost}
+ c = %{one: prize.x, two: prize.y}
+
+ a_presses = (b.two * c.one - b.one * c.two) / (b.two * a.one - b.one * a.two)
- result =
- for(
- a_press <- 0..a_presses,
- b_press <- 0..b_presses,
- do: {a_press, b_press, a.cost * a_press + b.cost * b_press}
- )
- |> Enum.sort_by(fn {_, _, cost} -> cost end)
- |> Enum.find(fn {a_press, b_press, _cost} ->
- a_press * a.x + b_press * b.x == prize.x && a_press * a.y + b_press * b.y == prize.y
- end)
+ # The last part should really be b.one but doesn't need the cost factor applied
+ b_presses = (a.one * trunc(a_presses) - c.one) / (-1 * button_b.x)
- if result do
+ # Fractional values aren't valid - you can't press a button 0.5 times
+ if a_presses == trunc(a_presses) && b_presses == trunc(b_presses) do
%{
- a: %{tokens: a.cost * elem(result, 0), presses: elem(result, 0)},
- b: %{tokens: b.cost * elem(result, 1), presses: elem(result, 1)}
+ a: %{presses: trunc(a_presses), tokens: trunc(a_presses) * button_a.cost},
+ b: %{presses: trunc(b_presses), tokens: trunc(b_presses) * button_b.cost}
}
+ else
+ nil
end
end
@doc """
- iex> Day13.parse_input("Button A: X+94, Y+34\\nButton B: X+22, Y+67\\nPrize: X=8400, Y=5400")
+ iex> Day13.parse_input("Button A: X+94, Y+34\\nButton B: X+22, Y+67\\nPrize: X=8400, Y=5400", 0)
[%{buttons: %{a: %{x: 94, y: 34, cost: 3}, b: %{x: 22, y: 67, cost: 1}}, prize: %{x: 8400, y: 5400}}]
"""
- def parse_input(input) do
+ def parse_input(input, offset) do
input
|> String.split("\n\n", trim: true)
|> Enum.map(fn machine ->
[a, b, prize] = String.split(machine, "\n", trim: true)
- %{buttons: %{a: parse_button(a, 3), b: parse_button(b, 1)}, prize: parse_prize(prize)}
+
+ %{
+ buttons: %{a: parse_button(a, 3), b: parse_button(b, 1)},
+ prize: parse_prize(prize, offset)
+ }
end)
end
@@ -67,11 +82,11 @@ defmodule Y2024.Day13 do
%{cost: cost, x: String.to_integer(x), y: String.to_integer(y)}
end
- defp parse_prize(string) do
+ defp parse_prize(string, offset) do
[[x], [y]] = Regex.scan(~r/\d+/, string)
- %{x: String.to_integer(x), y: String.to_integer(y)}
+ %{x: String.to_integer(x) + offset, y: String.to_integer(y) + offset}
end
- def part1_verify, do: input() |> parse_input() |> part1()
- # def part2_verify, do: input() |> parse_input() |> part2()
+ def part1_verify, do: input() |> parse_input(0) |> part1()
+ def part2_verify, do: input() |> parse_input(10_000_000_000_000) |> part2()
end
diff --git a/test/y2024/day13_test.exs b/test/y2024/day13_test.exs
index 0bcdf78..3dfbd01 100644
--- a/test/y2024/day13_test.exs
+++ b/test/y2024/day13_test.exs
@@ -4,5 +4,5 @@ defmodule Y2024.Day13Test do
doctest Day13
test "verification, part 1", do: assert(Day13.part1_verify() == 27157)
- # test "verification, part 2", do: assert(Day13.part2_verify() == "update or delete me")
+ test "verification, part 2", do: assert(Day13.part2_verify() == 104_015_411_578_548)
end