From b414a9d34901a9296d12d5257d5be62a487d1d30 Mon Sep 17 00:00:00 2001 From: Jeremy Kaplan Date: Sun, 17 Dec 2023 13:56:50 -0800 Subject: [PATCH] 2023 Day 17 Part 1 --- 2023/day17.nim | 147 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 2023/day17.nim diff --git a/2023/day17.nim b/2023/day17.nim new file mode 100644 index 0000000..7f02280 --- /dev/null +++ b/2023/day17.nim @@ -0,0 +1,147 @@ +import std/algorithm +import std/deques +import std/enumerate +import std/heapqueue +import std/math +import std/options +import std/sequtils +import std/sets +import std/strscans +import std/strutils +import std/sugar +import std/tables +import std/terminal +import std/times + +proc dbg[T](v: T): T = (echo(v); v) + +func empty[T](v: seq[T]): bool = v.len == 0 + +proc split[T](v: seq[T], pred: proc(elt: T): bool {.noSideEffect.}): seq[seq[T]] = + var current: seq[T] + for elt in v: + if pred(elt): + result.add current + current = @[] + else: + current.add elt + result.add current + +func join[T](parts: seq[seq[T]], sep: T): seq[T] = + if parts.len > 0: + result &= parts[0] + + for part in parts[1..^1]: + result.add sep + result &= part + +type Point = tuple + r: int + c: int + +type Map = object + height: int + width: int + grid: Table[Point, int] + +func parseMap(text: string): Map = + let lines = text.strip.splitLines() + result.height = lines.len + result.width = lines[0].len + + for (r, line) in enumerate(lines): + for (c, sym) in enumerate(line): + result.grid[(r, c)] = sym.int - '0'.int + +type Direction = enum + N + W + S + E + +func cw(d: Direction): Direction = + case d: + of N: E + of E: S + of S: W + of W: N + +func ccw(d: Direction): Direction = + case d: + of N: W + of W: S + of S: E + of E: N + +func move(p: Point, d: Direction, n: int): Point = + case d: + of N: (p.r - n, p.c) + of S: (p.r + n, p.c) + of W: (p.r, p.c - n) + of E: (p.r, p.c + n) + +func inBounds(m: Map, p: Point): bool = + p.r in 0 ..< m.height and p.c in 0 ..< m.width + +func loss(m: Map, p: Point): int = + if m.inBounds(p): + return m.grid[p] + +type State = tuple + pos: Point + dir: Direction + +type Node = tuple + state: State + cost: int + +func `<`(a, b: Node): bool = a.cost < b.cost + + +iterator successors(m: Map, state: State): (State, int) = + for steps in 1..3: + var pos = state.pos + var loss = 0 + for _ in 1..steps: + pos = pos.move(state.dir, 1) + loss += m.loss(pos) + + yield ((pos, state.dir.cw), loss) + yield ((pos, state.dir.ccw), loss) + +proc heatLoss(m: Map, start, goal: Point): int = + var queue: HeapQueue[Node] + var seen: HashSet[State] + + # Unknown starting direction, so try them all. + queue.push ((start, N), 0) + queue.push ((start, S), 0) + queue.push ((start, E), 0) + queue.push ((start, W), 0) + + while queue.len > 0: + let (state, cost) = queue.pop + + if not m.inBounds(state.pos): continue + + if state in seen: continue + seen.incl state + + if state.pos == goal: + return cost + + for (next, extra) in m.successors(state): + queue.push (next, cost + extra) + +proc part1(input: string): int = + let text = readFile(input) + let map = parseMap(text) + map.heatLoss((0, 0), (map.height - 1, map.width - 1)) + +proc timed[T](f: () -> T): T = + let start = cpuTime() + result = f() + echo "Time :", cpuTime() - start + +echo timed(proc(): int = part1("input/test.txt")) +echo timed(proc(): int = part1("input/day17.txt"))