Advent of Code #13 (in Gleam)

This was yet another problem that my boss helped me out with for part 2. I honestly don't know if I would have ever figured out the trick. I used an iterative approach to part 1 that clearly wouldn't work for part 2. Again, the wording of the problem doesn't make it obvious that you can just solve it as a system of equations, but that's what makes these puzzles fun! I kept the iterative approach for part 1 in the code because I actually kind of like how it was done. But obviously the solution for part 2 could be applied much more effectively. import gleam/int import gleam/io import gleam/list import gleam/string import gleam/option.{type Option, Some, None} import gleam/regexp.{Match} import simplifile as file const example = " 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 " const template = " Button A: X\\+(\\d+), Y\\+(\\d+) Button B: X\\+(\\d+), Y\\+(\\d+) Prize: X=(\\d+), Y=(\\d+) " pub fn main() { let assert Ok(input) = file.read("input") let assert 480 = part1(example) part1(input) |> int.to_string |> io.println part2(input) |> int.to_string |> io.println } type Position = #(Int, Int) type ClawMachine { ClawMachine(a: Position, b: Position, prize: Position) } fn assert_parse_int(input: String) -> Int { let assert Ok(x) = int.parse(input) x } fn cost(claw_machine: ClawMachine, a: Int, b: Int) -> Option(Int) { let ClawMachine(button_a, button_b, prize) = claw_machine let #(a_x, a_y) = button_a let #(b_x, b_y) = button_b let position = #(a * a_x + b * b_x, a * a_y + b * b_y) case position == prize { True -> Some(a * 3 + b) False -> None } } fn parse_input(input: String) -> List(ClawMachine) { let assert Ok(re) = template |> string.trim() |> regexp.from_string() regexp.scan(re, input) |> list.map(fn(match) { let assert Match(_, [Some(a_x), Some(a_y), Some(b_x), Some(b_y), Some(prize_x), Some(prize_y)]) = match ClawMachine( #(assert_parse_int(a_x), assert_parse_int(a_y)), #(assert_parse_int(b_x), assert_parse_int(b_y)), #(assert_parse_int(prize_x), assert_parse_int(prize_y)) ) }) } fn play(claw_machine: ClawMachine) -> Int { list.range(0, 100) |> list.fold(500, fn(min, a) { list.range(0, 100) |> list.fold(min, fn(min, b) { let a_b = case cost(claw_machine, a, b) { Some(cost) -> int.min(min, cost) None -> min } case cost(claw_machine, b, a) { Some(cost) -> int.min(min, cost) None -> min } }) }) |> fn(min) { case min == 500 { True -> 0 False -> min } } } fn part1(input: String) -> Int { input |> parse_input() |> list.fold(0, fn(sum, claw_machine) { sum + play(claw_machine) }) } fn solve_equation(claw_machine: ClawMachine) -> Int { let ClawMachine(#(ax, ay), #(bx, by), #(px, py)) = claw_machine let b_rem = {py * ax - ay * px} % {ax * by - ay * bx} case b_rem == 0 { True -> { let b = {py * ax - ay * px} / {ax * by - ay * bx} let a_rem = {px - bx * b} % ax case a_rem == 0 { True -> { let a = {px - bx * b} / ax a * 3 + b } False -> 0 } } False -> 0 } } fn part2(input: String) -> Int { input |> parse_input() |> list.fold(0, fn(sum, claw_machine) { let ClawMachine(_, _, #(px, py)) = claw_machine let corrected = ClawMachine(..claw_machine, prize: #(px + 10000000000000, py + 10000000000000)) sum + solve_equation(corrected) }) }

Mar 10, 2025 - 06:08
 0
Advent of Code #13 (in Gleam)

This was yet another problem that my boss helped me out with for part 2. I honestly don't know if I would have ever figured out the trick. I used an iterative approach to part 1 that clearly wouldn't work for part 2. Again, the wording of the problem doesn't make it obvious that you can just solve it as a system of equations, but that's what makes these puzzles fun!

I kept the iterative approach for part 1 in the code because I actually kind of like how it was done. But obviously the solution for part 2 could be applied much more effectively.

import gleam/int
import gleam/io
import gleam/list
import gleam/string
import gleam/option.{type Option, Some, None}
import gleam/regexp.{Match}
import simplifile as file

const example = "
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
"

const template = "
Button A: X\\+(\\d+), Y\\+(\\d+)
Button B: X\\+(\\d+), Y\\+(\\d+)
Prize: X=(\\d+), Y=(\\d+)
"

pub fn main() {
  let assert Ok(input) = file.read("input")
  let assert 480 = part1(example)
  part1(input) |> int.to_string |> io.println
  part2(input) |> int.to_string |> io.println
}

type Position = #(Int, Int)
type ClawMachine {
  ClawMachine(a: Position, b: Position, prize: Position)
}

fn assert_parse_int(input: String) -> Int {
  let assert Ok(x) = int.parse(input)
  x
}

fn cost(claw_machine: ClawMachine, a: Int, b: Int) -> Option(Int) {
  let ClawMachine(button_a, button_b, prize) = claw_machine
  let #(a_x, a_y) = button_a
  let #(b_x, b_y) = button_b
  let position = #(a * a_x + b * b_x, a * a_y + b * b_y)
  case position == prize {
    True -> Some(a * 3 + b)
    False -> None
  }
}

fn parse_input(input: String) -> List(ClawMachine) {
  let assert Ok(re) = template |> string.trim() |> regexp.from_string()
  regexp.scan(re, input)
  |> list.map(fn(match) {
    let assert Match(_, [Some(a_x), Some(a_y), Some(b_x), Some(b_y), Some(prize_x), Some(prize_y)]) = match
    ClawMachine(
      #(assert_parse_int(a_x), assert_parse_int(a_y)),
      #(assert_parse_int(b_x), assert_parse_int(b_y)),
      #(assert_parse_int(prize_x), assert_parse_int(prize_y))
    )
  })
}

fn play(claw_machine: ClawMachine) -> Int {
  list.range(0, 100)
  |> list.fold(500, fn(min, a) {
    list.range(0, 100)
    |> list.fold(min, fn(min, b) {
      let a_b = 
        case cost(claw_machine, a, b) {
          Some(cost) -> int.min(min, cost)
          None -> min
        }
      case cost(claw_machine, b, a) {
        Some(cost) -> int.min(min, cost)
        None -> min
      }
    })
  })
  |> fn(min) {
    case min == 500 {
      True -> 0
      False -> min
    }
  }
}

fn part1(input: String) -> Int {
  input
  |> parse_input()
  |> list.fold(0, fn(sum, claw_machine) {
    sum + play(claw_machine)
  })
}

fn solve_equation(claw_machine: ClawMachine) -> Int {
  let ClawMachine(#(ax, ay), #(bx, by), #(px, py)) = claw_machine
  let b_rem = {py * ax - ay * px} % {ax * by - ay * bx}
  case b_rem == 0 {
    True -> {
      let b = {py * ax - ay * px} / {ax * by - ay * bx}
      let a_rem = {px - bx * b} % ax
      case a_rem == 0 {
        True -> {
          let a = {px - bx * b} / ax
          a * 3 + b
        }
        False -> 0
      }
    }
    False -> 0
  }
}

fn part2(input: String) -> Int {
  input
  |> parse_input()
  |> list.fold(0, fn(sum, claw_machine) {
    let ClawMachine(_, _, #(px, py)) = claw_machine
    let corrected = ClawMachine(..claw_machine, prize: #(px + 10000000000000, py + 10000000000000))
    sum + solve_equation(corrected)
  })
}