From 6f2ce7d158d6c450ce1a95a3cd4834ab665a53de Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 15 Dec 2024 16:50:28 -0600 Subject: [PATCH] Day 15 --- src/aoc/runner/year2024.gleam | 3 + src/aoc/year2024/day15.gleam | 160 +++++++++++++++++++++++++++++ test/aoc/year2024/day15_test.gleam | 52 ++++++++++ 3 files changed, 215 insertions(+) create mode 100644 src/aoc/year2024/day15.gleam create mode 100644 test/aoc/year2024/day15_test.gleam diff --git a/src/aoc/runner/year2024.gleam b/src/aoc/runner/year2024.gleam index a6525d6..c54b2ce 100644 --- a/src/aoc/runner/year2024.gleam +++ b/src/aoc/runner/year2024.gleam @@ -12,6 +12,7 @@ import aoc/year2024/day11 import aoc/year2024/day12 import aoc/year2024/day13 import aoc/year2024/day14 +import aoc/year2024/day15 import gleam/string pub fn run(input: String, day: Int, part: Int) { @@ -44,6 +45,8 @@ pub fn run(input: String, day: Int, part: Int) { 13, 2 -> input |> day13.part2 |> string.inspect 14, 1 -> input |> day14.part1(101, 103) |> string.inspect 14, 2 -> input |> day14.part2(101, 103) |> string.inspect + 15, 1 -> input |> day15.part1 |> string.inspect + 15, 2 -> input |> day15.part2 |> string.inspect _, _ -> "Unknown day and part for 2024: day " <> string.inspect(day) diff --git a/src/aoc/year2024/day15.gleam b/src/aoc/year2024/day15.gleam new file mode 100644 index 0000000..54fa56f --- /dev/null +++ b/src/aoc/year2024/day15.gleam @@ -0,0 +1,160 @@ +import aoc/util/str +import gleam/dict.{type Dict} +import gleam/list +import gleam/result +import gleam/string + +type Coord = + #(Int, Int) + +type Grid = + Dict(Coord, String) + +type Dir { + Up + Down + Left + Right +} + +fn make_grid(str: String, f: fn(String) -> List(String)) -> Grid { + str + |> str.lines + |> list.index_map(fn(l, y) { + l + |> string.to_graphemes + |> list.flat_map(f) + |> list.index_map(fn(c, x) { #(#(x, y), c) }) + }) + |> list.flatten + |> dict.from_list +} + +fn parse(input: String, f: fn(String) -> List(String)) -> #(Grid, List(Dir)) { + let assert Ok(#(a, b)) = string.split_once(input, "\n\n") + let dirs = + b + |> str.lines + |> list.flat_map(fn(l) { string.to_graphemes(l) }) + |> list.filter_map(fn(c) { + case c { + "^" -> Ok(Up) + "v" -> Ok(Down) + "<" -> Ok(Left) + ">" -> Ok(Right) + _ -> Error(Nil) + } + }) + #(make_grid(a, f), dirs) +} + +fn find_robot(grid: Grid) -> Coord { + grid + |> dict.to_list + |> list.find(fn(p) { p.1 == "@" }) + |> result.map(fn(p) { p.0 }) + |> result.unwrap(#(0, 0)) +} + +fn next(grid: Grid, coord: Coord, dir: Dir) -> Result(#(Coord, String), Nil) { + let #(x, y) = coord + let next_c = case dir { + Up -> #(x, y - 1) + Down -> #(x, y + 1) + Left -> #(x - 1, y) + Right -> #(x + 1, y) + } + use v <- result.map(dict.get(grid, next_c)) + #(next_c, v) +} + +fn push( + grid: Grid, + coord: Coord, + v: String, + rep: String, + dir: Dir, +) -> Result(Grid, Nil) { + let grid = dict.insert(grid, coord, rep) + use #(next_c, next_v) <- result.try(next(grid, coord, dir)) + let grid = dict.insert(grid, next_c, v) + case next_v { + "." -> Ok(grid) + "O" -> push(grid, next_c, next_v, v, dir) + _ -> Error(Nil) + } +} + +fn push2( + grid: Grid, + coord: Coord, + v: String, + rep: String, + dir: Dir, +) -> Result(Grid, Nil) { + let grid = dict.insert(grid, coord, rep) + use #(next_c, next_v) <- result.try(next(grid, coord, dir)) + let grid = dict.insert(grid, next_c, v) + case next_v { + "." -> Ok(grid) + "[" | "]" if dir == Left || dir == Right -> + push2(grid, next_c, next_v, v, dir) + "[" -> { + use grid <- result.try(push2(grid, next_c, next_v, v, dir)) + push2(grid, #(next_c.0 + 1, next_c.1), "]", ".", dir) + } + "]" -> { + use grid <- result.try(push2(grid, next_c, next_v, v, dir)) + push2(grid, #(next_c.0 - 1, next_c.1), "[", ".", dir) + } + _ -> Error(Nil) + } +} + +fn expand(c: String) -> List(String) { + case c { + "#" -> ["#", "#"] + "O" -> ["[", "]"] + "@" -> ["@", "."] + _ -> [".", "."] + } +} + +pub fn part1(input: String) -> Int { + let #(grid, dirs) = parse(input, fn(c) { [c] }) + let robot = find_robot(grid) + let #(grid, _) = + list.fold(dirs, #(grid, robot), fn(acc, dir) { + let #(grid, robot) = acc + let res = { + use next_g <- result.try(push(grid, robot, "@", ".", dir)) + use #(next_r, _) <- result.map(next(grid, robot, dir)) + #(next_g, next_r) + } + result.unwrap(res, acc) + }) + grid + |> dict.filter(fn(_k, v) { v == "O" }) + |> dict.keys + |> list.fold(0, fn(s, k) { s + k.0 + 100 * k.1 }) +} + +pub fn part2(input: String) -> Int { + let #(grid, dirs) = parse(input, expand) + let robot = find_robot(grid) + let #(grid, _) = + list.fold(dirs, #(grid, robot), fn(acc, dir) { + let #(grid, robot) = acc + let res = { + use next_g <- result.try(push2(grid, robot, "@", ".", dir)) + use #(next_r, _) <- result.map(next(grid, robot, dir)) + #(next_g, next_r) + } + result.unwrap(res, acc) + }) + + grid + |> dict.filter(fn(_k, v) { v == "[" }) + |> dict.keys + |> list.fold(0, fn(s, k) { s + k.0 + 100 * k.1 }) +} diff --git a/test/aoc/year2024/day15_test.gleam b/test/aoc/year2024/day15_test.gleam new file mode 100644 index 0000000..07c706b --- /dev/null +++ b/test/aoc/year2024/day15_test.gleam @@ -0,0 +1,52 @@ +import aoc/year2024/day15 +import glacier/should + +const input1 = "######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^^>>>vv>v<<" + +const input2 = "########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^ +" + +pub fn part1_test() { + input1 + |> day15.part1 + |> should.equal(2028) + + input2 + |> day15.part1 + |> should.equal(10_092) +} + +pub fn part2_test() { + input2 + |> day15.part2 + |> should.equal(9021) +}