aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--aoc2023/src/day17/solve.gleam104
-rw-r--r--aoc2023/src/utilities/array2d.gleam19
2 files changed, 120 insertions, 3 deletions
diff --git a/aoc2023/src/day17/solve.gleam b/aoc2023/src/day17/solve.gleam
index 431dcc7..200565a 100644
--- a/aoc2023/src/day17/solve.gleam
+++ b/aoc2023/src/day17/solve.gleam
@@ -1,11 +1,111 @@
import adglent.{First, Second}
import gleam/io
import gleam/string
+import gleam/list
+import gleam/result
import utilities/array2d.{type Posn, Posn}
+import gleam/bool
+import gleam/dict
+import gleam/queue.{type Queue}
+import gleam/set
+
+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 })
+ }
+}
+
+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, goal, seen, queue: Queue(State), neighbor_fn, goal_fn) {
+ let assert Ok(#(s, rest)) = queue.pop_front(queue)
+ let key = make_key(s)
+
+ case set.contains(seen, key) {
+ True -> find_path(grid, goal, seen, rest, neighbor_fn, goal_fn)
+ False -> {
+ let seen = set.insert(seen, key)
+ let neighbors: List(State) = neighbor_fn(s)
+
+ case list.find(neighbors, goal_fn) {
+ Ok(final) -> final.heatloss
+ Error(Nil) -> {
+ let queue = list.fold(neighbors, queue, queue.push_back)
+ find_path(grid, goal, seen, queue, neighbor_fn, goal_fn)
+ }
+ }
+ }
+ }
+}
pub fn part1(input: String) {
- input
- |> array2d.parse_grid
+ 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 goal = Posn(rmax, cmax)
+
+ find_path(
+ grid,
+ goal,
+ set.new(),
+ queue.from_list([State(Posn(0, 0), 0, Posn(0, 0), [])]),
+ find_good_neighbors(0, 3, _, grid),
+ is_goal(_, 1, goal),
+ )
|> string.inspect
}
diff --git a/aoc2023/src/utilities/array2d.gleam b/aoc2023/src/utilities/array2d.gleam
index 83ec8ae..9d3b966 100644
--- a/aoc2023/src/utilities/array2d.gleam
+++ b/aoc2023/src/utilities/array2d.gleam
@@ -1,6 +1,7 @@
import gleam/list
import gleam/dict.{type Dict}
import gleam/string
+import gleam/int
pub type Posn {
Posn(r: Int, c: Int)
@@ -25,9 +26,25 @@ pub fn to_2d_array(xss: List(List(a))) -> Array2D(a) {
|> dict.from_list
}
-pub fn parse_grid(str: String) -> Array2D(String) {
+pub fn to_2d_intarray(xss: List(List(String))) -> Array2D(Int) {
+ {
+ use r, row <- list.index_map(xss)
+ use c, cell <- list.index_map(row)
+ let assert Ok(n) = int.parse(cell)
+ #(Posn(r, c), n)
+ }
+ |> list.flatten
+ |> dict.from_list
+}
+
+pub fn to_list_of_lists(str: String) -> List(List(String)) {
str
|> string.split("\n")
|> list.map(string.to_graphemes)
+}
+
+pub fn parse_grid(str: String) -> Array2D(String) {
+ str
+ |> to_list_of_lists
|> to_2d_array
}