aboutsummaryrefslogtreecommitdiff
path: root/aoc2023-gleam/src/day17/solve.gleam
diff options
context:
space:
mode:
Diffstat (limited to 'aoc2023-gleam/src/day17/solve.gleam')
-rw-r--r--aoc2023-gleam/src/day17/solve.gleam143
1 files changed, 143 insertions, 0 deletions
diff --git a/aoc2023-gleam/src/day17/solve.gleam b/aoc2023-gleam/src/day17/solve.gleam
new file mode 100644
index 0000000..7a01c4d
--- /dev/null
+++ b/aoc2023-gleam/src/day17/solve.gleam
@@ -0,0 +1,143 @@
+import adglent.{First, Second}
+import gleam/bool
+import gleam/dict.{type Dict}
+import gleam/io
+import gleam/list
+import gleam/result
+import gleam/string
+import gleam/set.{type Set}
+import utilities/array2d.{type Posn, Posn}
+import utilities/prioqueue.{type PriorityQueue}
+
+type State {
+ State(posn: Posn, heatloss: Int, previous: Posn, history: List(Posn))
+}
+
+const deltas = [Posn(-1, 0), Posn(1, 0), Posn(0, -1), Posn(0, 1)]
+
+fn make_key(s: State) {
+ #(s.posn, same_dir(s))
+}
+
+fn same_dir(s: State) {
+ case s.history {
+ [] -> []
+ [first, ..] as deltas ->
+ list.take_while(deltas, fn(d) { d == first })
+ |> list.take(10)
+ }
+}
+
+fn is_goal(s: State, min_run: Int, goal: Posn) {
+ goal == s.posn && list.length(same_dir(s)) >= min_run
+}
+
+fn find_good_neighbors(max: Int, min: Int, s: State, grid) {
+ deltas
+ |> list.filter(eliminate_bad_neighbors(_, s, max, min, grid))
+ |> list.map(make_state(_, s, grid))
+}
+
+fn eliminate_bad_neighbors(d: Posn, s: State, max, min, grid) {
+ let neighbor = array2d.add_posns(d, s.posn)
+
+ use <- bool.guard(
+ neighbor == s.previous || !dict.has_key(grid, neighbor),
+ False,
+ )
+ case same_dir(s), list.length(same_dir(s)) {
+ [prev, ..], l if l == max -> d != prev
+ _, 0 -> True
+ [prev, ..], l if l < min -> d == prev
+ _, _ -> True
+ }
+}
+
+fn make_state(d: Posn, s: State, grid) {
+ let neighbor = array2d.add_posns(d, s.posn)
+ let assert Ok(heat_lost) = dict.get(grid, neighbor)
+ State(
+ posn: neighbor,
+ heatloss: s.heatloss + heat_lost,
+ previous: s.posn,
+ history: [d, ..s.history],
+ )
+}
+
+fn find_path(
+ grid: Dict(Posn, Int),
+ queue: PriorityQueue(State),
+ seen: Set(#(Posn, List(Posn))),
+ get_neighbors: fn(State) -> List(State),
+ is_goal: fn(State) -> Bool,
+) {
+ let assert Ok(#(state, rest)) = prioqueue.pop(queue)
+ let key =
+ make_key(
+ state
+ |> io.debug,
+ )
+ case set.contains(seen, key) {
+ True -> find_path(grid, rest, seen, get_neighbors, is_goal)
+ False -> {
+ let now_seen = set.insert(seen, key)
+ let neighbors = get_neighbors(state)
+ case list.find(neighbors, is_goal) {
+ Ok(final) -> final.heatloss
+ _err -> {
+ let now_queue =
+ list.fold(neighbors, rest, fn(acc, n) {
+ prioqueue.insert(acc, n, n.heatloss)
+ })
+ find_path(grid, now_queue, now_seen, get_neighbors, is_goal)
+ }
+ }
+ }
+ }
+}
+
+pub fn part1(input: String) {
+ let raw_grid =
+ input
+ |> array2d.to_list_of_lists
+
+ let grid = array2d.to_2d_intarray(raw_grid)
+
+ let rmax = list.length(raw_grid)
+ let assert Ok(cmax) =
+ raw_grid
+ |> list.first
+ |> result.map(list.length)
+
+ let start = State(Posn(0, 0), 0, Posn(0, 0), [])
+ let goal = Posn(rmax, cmax)
+
+ find_path(
+ grid,
+ prioqueue.insert(prioqueue.new(), start, 0),
+ set.new(),
+ find_good_neighbors(0, 3, _, grid),
+ is_goal(_, 1, goal),
+ )
+ |> string.inspect
+}
+
+pub fn part2(input: String) {
+ input
+ |> string.inspect
+}
+
+pub fn main() {
+ let assert Ok(part) = adglent.get_part()
+ let assert Ok(input) = adglent.get_input("17")
+ case part {
+ First ->
+ part1(input)
+ |> adglent.inspect
+ |> io.println
+ Second ->
+ part2(input)
+ |> adglent.inspect
+ |> io.println
+ }
+}