diff --git a/2023/day18.nim b/2023/day18.nim new file mode 100644 index 0000000..60213fb --- /dev/null +++ b/2023/day18.nim @@ -0,0 +1,185 @@ +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 Direction = enum + N + W + S + E + +func parseDirection(s: string): Direction = + case s: + of "U": N + of "D": S + of "L": W + of "R": E + else: + raise newException(ValueError, s) + +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 + +type Instruction = tuple + dir: Direction + steps: int + color: string + +func parseInstruction(line: string): Instruction = + var dir: string + if not scanf(line, "$+ $i ($+)$.", dir, result.steps, result.color): + raise newException(ValueError, line) + result.dir = dir.parseDirection + +func parseInstructions(text: string): seq[Instruction] = + text.strip.splitLines.map(parseInstruction) + +type Point = tuple + r: int + c: int + +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) + +type Lagoon = object + grid: Table[Point, char] + + rlo, rhi: int + clo, chi: int + +func assign(l: var Lagoon, p: Point, sym: char) = + l.grid[p] = sym + + l.rlo = min(l.rlo, p.r) + l.rhi = max(l.rhi, p.r) + l.clo = min(l.clo, p.c) + l.chi = max(l.chi, p.c) + +func dig(l: var Lagoon, p: Point) = + l.assign(p, '#') + +func inBoundsPadding(l: Lagoon, p: Point): bool = + p.r in (l.rlo - 1) .. (l.rhi + 1) and p.c in (l.clo - 1) .. (l.chi + 1) + +func neighborsFill(l: Lagoon, p: Point): seq[Point] = + for (dr, dc) in [ + (-1, 0), # Up + (+1, 0), # Down + (0, -1), # Left + (0, +1), # Right + ]: + let n = (p.r + dr, p.c + dc) + if l.inBoundsPadding(n): + result.add n + +proc floodFill(l: var Lagoon, start: Point, sym: char) = + var queue: Deque[Point] + + queue.addLast start + + while queue.len > 0: + let p = queue.popFirst + + if p in l.grid: continue + l.grid[p] = sym + + for n in l.neighborsFill(p): + queue.addLast n + +proc fill(l: var Lagoon) = + let empty = (l.rlo - 1, l.clo - 1) + l.floodFill(empty, '.') + + for r in l.rlo .. l.rhi: + for c in l.clo .. l.chi: + let p = (r, c) + if p notin l.grid: + l.dig(p) + +func execute(l: var Lagoon, instructions: seq[Instruction]) = + var pos: Point = (0, 0) + l.dig(pos) + + for i in instructions: + for _ in 1 .. i.steps: + pos = pos.move(i.dir, 1) + l.dig(pos) + +func render(l: Lagoon): string = + for r in l.rlo .. l.rhi: + for c in l.clo .. l.chi: + result &= l.grid.getOrDefault((r, c), ' ') + result &= '\n' + +func countDug(l: Lagoon): int = + for r in l.rlo .. l.rhi: + for c in l.clo .. l.chi: + if l.grid[(r, c)] == '#': + result.inc + +proc part1(input: string): int = + let text = readFile(input) + let instructions = parseInstructions(text) + + var lagoon: Lagoon + lagoon.execute(instructions) + lagoon.fill + lagoon.countDug + +################### + +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/day18.txt"))