Skip to content

Commit

Permalink
2024 day 13, part 2
Browse files Browse the repository at this point in the history
I looked at this problem, said yep, we've got some simultaneous equations
with a bit of a cost factor built in, and then promptly *forgot* they can
be solved computationally.

This puzzle made me feel pretty silly.
  • Loading branch information
sevenseacat committed Dec 13, 2024
1 parent b5b858b commit d8fd82a
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 36 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
My Elixir solutions for [Advent of Code](https://adventofcode.com/) (all years).

<!-- stars start -->
<p><img src="https://img.shields.io/static/v1?label=Total&message=444%20stars&style=for-the-badge&color=green" alt="444 stars" /></p>
<p><a href="./lib/y2024/"><img src="https://img.shields.io/static/v1?label=2024&message=25%20stars&style=for-the-badge&color=yellow" alt="25 stars" /></a><br />
<p><img src="https://img.shields.io/static/v1?label=Total&message=445%20stars&style=for-the-badge&color=green" alt="445 stars" /></p>
<p><a href="./lib/y2024/"><img src="https://img.shields.io/static/v1?label=2024&message=26%20stars&style=for-the-badge&color=yellow" alt="26 stars" /></a><br />
<a href="./lib/y2023/"><img src="https://img.shields.io/static/v1?label=2023&message=44%20stars&style=for-the-badge&color=green" alt="44 stars" /></a><br />
<a href="./lib/y2022/"><img src="https://img.shields.io/static/v1?label=2022&message=%E2%AD%90%EF%B8%8F%2050%20stars%20%E2%AD%90%EF%B8%8F&style=for-the-badge&color=brightgreen" alt="50 stars" /></a><br />
<a href="./lib/y2021/"><img src="https://img.shields.io/static/v1?label=2021&message=46%20stars&style=for-the-badge&color=green" alt="46 stars" /></a><br />
Expand Down
5 changes: 3 additions & 2 deletions lib/y2024/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

My Elixir solutions for [Advent of Code 2024](https://adventofcode.com/2024).

<!-- stars 2024 start --><img src="https://img.shields.io/static/v1?label=2024&message=25%20stars&style=for-the-badge&color=yellow" alt="25 stars" /><!-- stars 2024 end -->
<!-- stars 2024 start --><img src="https://img.shields.io/static/v1?label=2024&message=26%20stars&style=for-the-badge&color=yellow" alt="26 stars" /><!-- stars 2024 end -->

## Benchmarks

Expand Down Expand Up @@ -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
```
77 changes: 46 additions & 31 deletions lib/y2024/day13.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
2 changes: 1 addition & 1 deletion test/y2024/day13_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit d8fd82a

Please sign in to comment.