diff --git a/sample/day13-sample.txt b/sample/day13-sample.txt new file mode 100644 index 0000000..912f482 --- /dev/null +++ b/sample/day13-sample.txt @@ -0,0 +1,15 @@ +Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400 + +Button A: X+26, Y+66 +Button B: X+67, Y+21 +Prize: X=12748, Y=12176 + +Button A: X+17, Y+86 +Button B: X+84, Y+37 +Prize: X=7870, Y=6450 + +Button A: X+69, Y+23 +Button B: X+27, Y+71 +Prize: X=18641, Y=10279 diff --git a/src/day13_p1.gleam b/src/day13_p1.gleam new file mode 100644 index 0000000..652b681 --- /dev/null +++ b/src/day13_p1.gleam @@ -0,0 +1,105 @@ +import gleam/int +import gleam/io +import gleam/iterator +import gleam/list.{map, reduce} +import gleam/option.{type Option} +import gleam/string +import stdin.{stdin} + +pub fn main() { + stdin() + |> read_input + |> map(calculate_win_cost) + |> option.values + |> reduce(int.add) + |> io.debug +} + +fn calculate_win_cost(machine: ClawMachine) -> Option(Int) { + let Machine(a, b, prize) = machine + let #(a_x, a_y) = a + let #(b_x, b_y) = b + let #(p_x, p_y) = prize + + let a_min = min_presses(a, prize) + + list.range(0, a_min) + |> list.filter_map(fn(a_times) { + let prize_rem_x = p_x - a_x * a_times + let prize_rem_y = p_y - a_y * a_times + let b_times = min_presses(b, #(prize_rem_x, prize_rem_y)) + case prize_rem_x - b_x * b_times, prize_rem_y - b_y * b_times { + 0, 0 -> Ok(#(a_times, b_times)) + _, _ -> Error(Nil) + } + }) + |> map(calculate_cost) + |> reduce(int.min) + |> option.from_result +} + +fn calculate_cost(combo: #(Int, Int)) -> Int { + let #(a, b) = combo + a * 3 + b +} + +fn min_presses(advance: #(Int, Int), target: #(Int, Int)) -> Int { + let #(a_x, a_y) = advance + let #(t_x, t_y) = target + let assert Ok(a_min_x) = int.divide(t_x, a_x) + let assert Ok(a_min_y) = int.divide(t_y, a_y) + int.min(a_min_x, a_min_y) +} + +pub type ClawMachine { + Machine(a: #(Int, Int), b: #(Int, Int), prize: #(Int, Int)) +} + +fn read_input(it: iterator.Iterator(String)) -> List(ClawMachine) { + it + |> iterator.fold([], fn(acc, line) { + case string.trim(line) { + "Button A: " <> rest -> [ + Machine(parse_button(rest), #(0, 0), #(0, 0)), + ..acc + ] + "Button B: " <> rest -> acc |> patch_b(parse_button(rest)) + "Prize: " <> rest -> acc |> patch_prize(parse_prize(rest)) + _ -> acc + } + }) +} + +fn parse_button(text) -> #(Int, Int) { + let assert [Ok(x), Ok(y)] = + text + |> string.drop_start(2) + |> string.split(", Y+") + |> map(int.parse) + + #(x, y) +} + +fn parse_prize(text) -> #(Int, Int) { + let assert [Ok(x), Ok(y)] = + text + |> string.drop_start(2) + |> string.split(", Y=") + |> map(int.parse) + + #(x, y) +} + +fn patch_b(acc: List(ClawMachine), b: #(Int, Int)) -> List(ClawMachine) { + case acc { + [] -> [Machine(#(0, 0), b, #(0, 0))] + [Machine(a, _, p), ..rest] -> [Machine(a, b, p), ..rest] + } +} + +fn patch_prize(acc: List(ClawMachine), p: #(Int, Int)) -> List(ClawMachine) { + case acc { + [] -> [Machine(#(0, 0), #(0, 0), p)] + [Machine(a, b, _), ..rest] -> [Machine(a, b, p), ..rest] + } +} diff --git a/src/day13_p2.gleam b/src/day13_p2.gleam new file mode 100644 index 0000000..efdd420 --- /dev/null +++ b/src/day13_p2.gleam @@ -0,0 +1,101 @@ +import gleam/int +import gleam/io +import gleam/iterator +import gleam/list.{map, reduce} +import gleam/option.{type Option, None, Some} +import gleam/string +import stdin.{stdin} + +pub fn main() { + stdin() + |> read_input + |> map(calculate_win_cost) + |> option.values + |> reduce(int.add) + |> io.debug +} + +fn calculate_win_cost(machine: ClawMachine) -> Option(Int) { + let Machine(a, b, prize) = machine + let #(a_x, a_y) = a + let #(b_x, b_y) = b + let #(p_x, p_y) = prize + + let assert Ok(a_times) = + int.divide(b_y * p_x - b_x * p_y, a_x * b_y - a_y * b_x) + + let prize_rem_x = p_x - a_x * a_times + let prize_rem_y = p_y - a_y * a_times + let b_times = min_presses(b, #(prize_rem_x, prize_rem_y)) + case prize_rem_x - b_x * b_times, prize_rem_y - b_y * b_times { + 0, 0 -> Some(#(a_times, b_times)) + _, _ -> None + } + |> option.map(calculate_cost) +} + +fn calculate_cost(combo: #(Int, Int)) -> Int { + let #(a, b) = combo + a * 3 + b +} + +fn min_presses(advance: #(Int, Int), target: #(Int, Int)) -> Int { + let #(a_x, a_y) = advance + let #(t_x, t_y) = target + let assert Ok(a_min_x) = int.divide(t_x, a_x) + let assert Ok(a_min_y) = int.divide(t_y, a_y) + int.min(a_min_x, a_min_y) +} + +pub type ClawMachine { + Machine(a: #(Int, Int), b: #(Int, Int), prize: #(Int, Int)) +} + +fn read_input(it: iterator.Iterator(String)) -> List(ClawMachine) { + it + |> iterator.fold([], fn(acc, line) { + case string.trim(line) { + "Button A: " <> rest -> [ + Machine(parse_button(rest), #(0, 0), #(0, 0)), + ..acc + ] + "Button B: " <> rest -> acc |> patch_b(parse_button(rest)) + "Prize: " <> rest -> acc |> patch_prize(parse_prize(rest)) + _ -> acc + } + }) +} + +fn parse_button(text) -> #(Int, Int) { + let assert [Ok(x), Ok(y)] = + text + |> string.drop_start(2) + |> string.split(", Y+") + |> map(int.parse) + + #(x, y) +} + +fn parse_prize(text) -> #(Int, Int) { + let assert [Ok(x), Ok(y)] = + text + |> string.drop_start(2) + |> string.split(", Y=") + |> map(int.parse) + + #(x + 10_000_000_000_000, y + 10_000_000_000_000) +} + +fn patch_b(acc: List(ClawMachine), b: #(Int, Int)) -> List(ClawMachine) { + case acc { + [] -> [Machine(#(0, 0), b, #(0, 0))] + [Machine(a, _, p), ..rest] -> [Machine(a, b, p), ..rest] + } +} + +fn patch_prize(acc: List(ClawMachine), p: #(Int, Int)) -> List(ClawMachine) { + case acc { + [] -> [Machine(#(0, 0), #(0, 0), p)] + [Machine(a, b, _), ..rest] -> [Machine(a, b, p), ..rest] + } +}