2023 Day 10 Part 2
This commit is contained in:
parent
37cf9ec560
commit
dff1225024
1 changed files with 192 additions and 2 deletions
194
2023/day10.nim
194
2023/day10.nim
|
|
@ -2,12 +2,14 @@ import std/algorithm
|
||||||
import std/deques
|
import std/deques
|
||||||
import std/enumerate
|
import std/enumerate
|
||||||
import std/math
|
import std/math
|
||||||
|
import std/options
|
||||||
import std/sequtils
|
import std/sequtils
|
||||||
import std/sets
|
import std/sets
|
||||||
import std/strscans
|
import std/strscans
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/sugar
|
import std/sugar
|
||||||
import std/tables
|
import std/tables
|
||||||
|
import std/terminal
|
||||||
|
|
||||||
proc dbg[T](v: T): T = (echo(v); v)
|
proc dbg[T](v: T): T = (echo(v); v)
|
||||||
|
|
||||||
|
|
@ -33,8 +35,11 @@ func parseSketch(text: string): Sketch =
|
||||||
if sym == 'S':
|
if sym == 'S':
|
||||||
result.start = (r, c)
|
result.start = (r, c)
|
||||||
|
|
||||||
|
func inBounds(s: Sketch, p: Point): bool =
|
||||||
|
p.r in 0 ..< s.rows and p.c in 0 ..< s.cols
|
||||||
|
|
||||||
func pipeAt(s: Sketch, p: Point): char =
|
func pipeAt(s: Sketch, p: Point): char =
|
||||||
if p.r in 0 ..< s.rows and p.c in 0 ..< s.cols:
|
if s.inBounds(p):
|
||||||
s.grid[p]
|
s.grid[p]
|
||||||
else:
|
else:
|
||||||
'.'
|
'.'
|
||||||
|
|
@ -65,6 +70,8 @@ func neighbors(s: Sketch, p: Point): seq[Point] =
|
||||||
(0, +1, E, W), # Right E-W
|
(0, +1, E, W), # Right E-W
|
||||||
]:
|
]:
|
||||||
let n = (p.r + dr, p.c + dc)
|
let n = (p.r + dr, p.c + dc)
|
||||||
|
if not s.inBounds(n):
|
||||||
|
continue
|
||||||
if s.hasConn(p, pDir) and s.hasConn(n, nDir):
|
if s.hasConn(p, pDir) and s.hasConn(n, nDir):
|
||||||
result.add(n)
|
result.add(n)
|
||||||
|
|
||||||
|
|
@ -93,7 +100,190 @@ proc part1(): int =
|
||||||
let text = readFile("input/day10.txt")
|
let text = readFile("input/day10.txt")
|
||||||
let sketch = parseSketch(text)
|
let sketch = parseSketch(text)
|
||||||
|
|
||||||
for d in values(sketch.reachable):
|
let reachable = sketch.reachable
|
||||||
|
for d in values(reachable):
|
||||||
result = max(result, d)
|
result = max(result, d)
|
||||||
|
|
||||||
|
proc path(s: Sketch): seq[Point] =
|
||||||
|
var queue: Deque[seq[Point]]
|
||||||
|
|
||||||
|
queue.addLast @[s.start]
|
||||||
|
|
||||||
|
while queue.len > 0:
|
||||||
|
let path = queue.popFirst
|
||||||
|
let point = path[^1]
|
||||||
|
|
||||||
|
if path.len > 1 and s.pipeAt(point) == 'S':
|
||||||
|
return path
|
||||||
|
|
||||||
|
for n in s.neighbors(point):
|
||||||
|
if path.len > 1 and path[^2] == n:
|
||||||
|
continue
|
||||||
|
queue.addLast(path & @[n])
|
||||||
|
|
||||||
|
func inBoundsPadding(s: Sketch, p: Point): bool =
|
||||||
|
p.r in -1 .. s.rows and p.c in -1 .. s.cols
|
||||||
|
|
||||||
|
func neighborsFill(s: Sketch, 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 s.inBoundsPadding(n):
|
||||||
|
result.add n
|
||||||
|
|
||||||
|
proc floodFill(s: Sketch, start: Point, path: HashSet[Point]): HashSet[Point] =
|
||||||
|
var queue: Deque[Point]
|
||||||
|
|
||||||
|
queue.addLast start
|
||||||
|
|
||||||
|
while queue.len > 0:
|
||||||
|
let state = queue.popFirst
|
||||||
|
|
||||||
|
if state in result:
|
||||||
|
continue
|
||||||
|
result.incl state
|
||||||
|
|
||||||
|
for n in s.neighborsFill(state):
|
||||||
|
if n in path:
|
||||||
|
continue
|
||||||
|
queue.addLast n
|
||||||
|
|
||||||
|
func cells(s: Sketch): HashSet[Point] =
|
||||||
|
for r in 0 ..< s.rows:
|
||||||
|
for c in 0 ..< s.cols:
|
||||||
|
result.incl (r, c)
|
||||||
|
|
||||||
|
proc heading(a: Point, b: Point): Direction =
|
||||||
|
let dr = b.r - a.r
|
||||||
|
let dc = b.c - a.c
|
||||||
|
assert(abs(dr) + abs(dc) == 1)
|
||||||
|
|
||||||
|
if dr == -1: return N
|
||||||
|
if dr == +1: return S
|
||||||
|
if dc == -1: return W
|
||||||
|
if dc == +1: return E
|
||||||
|
assert(false)
|
||||||
|
|
||||||
|
func north(p: Point): Point = (p.r - 1, p.c)
|
||||||
|
func south(p: Point): Point = (p.r + 1, p.c)
|
||||||
|
func east(p: Point): Point = (p.r , p.c + 1)
|
||||||
|
func west(p: Point): Point = (p.r , p.c - 1)
|
||||||
|
|
||||||
|
func northwest(p: Point): Point = (p.r - 1, p.c - 1)
|
||||||
|
func northeast(p: Point): Point = (p.r - 1, p.c + 1)
|
||||||
|
func southwest(p: Point): Point = (p.r + 1, p.c - 1)
|
||||||
|
func southeast(p: Point): Point = (p.r + 1, p.c + 1)
|
||||||
|
|
||||||
|
func sides3(p: Point, h: Direction): (seq[Point], seq[Point]) =
|
||||||
|
case h:
|
||||||
|
of N: (
|
||||||
|
@[p.northwest, p.west, p.southwest],
|
||||||
|
@[p.northeast, p.east, p.southeast],
|
||||||
|
)
|
||||||
|
of S: (
|
||||||
|
@[p.southeast, p.east, p.northeast],
|
||||||
|
@[p.southwest, p.west, p.northwest],
|
||||||
|
)
|
||||||
|
of E: (
|
||||||
|
@[p.northeast, p.north, p.northwest],
|
||||||
|
@[p.southeast, p.south, p.southwest],
|
||||||
|
)
|
||||||
|
of W: (
|
||||||
|
@[p.southwest, p.south, p.southeast],
|
||||||
|
@[p.northwest, p.north, p.northeast],
|
||||||
|
)
|
||||||
|
|
||||||
|
func sides2(p: Point, h: Direction): (seq[Point], seq[Point]) =
|
||||||
|
let (l, r) = sides3(p, h)
|
||||||
|
(l[0..^2], r[0..^2])
|
||||||
|
|
||||||
|
proc walkPath(s: Sketch, path: seq[Point]): (HashSet[Point], HashSet[Point]) =
|
||||||
|
for (a, b) in zip(path[0..^2], path[1..^1]):
|
||||||
|
let h = a.heading(b)
|
||||||
|
|
||||||
|
let (left, right) = sides2(a, h)
|
||||||
|
for p in left:
|
||||||
|
result[0].incl p
|
||||||
|
for p in right:
|
||||||
|
result[1].incl p
|
||||||
|
|
||||||
|
type Side = enum
|
||||||
|
Left
|
||||||
|
Right
|
||||||
|
|
||||||
|
func whichSide(p: Point, left: HashSet[Point], right: HashSet[Point]): Option[Side] =
|
||||||
|
if p in left and p in right:
|
||||||
|
return none(Side)
|
||||||
|
if p in left:
|
||||||
|
return some(Left)
|
||||||
|
if p in right:
|
||||||
|
return some(Right)
|
||||||
|
return none(Side)
|
||||||
|
|
||||||
|
proc part2(): int =
|
||||||
|
let text = readFile("input/day10.txt")
|
||||||
|
let sketch = parseSketch(text)
|
||||||
|
|
||||||
|
let path = sketch.path
|
||||||
|
assert sketch.pipeAt(path[0]) == 'S'
|
||||||
|
assert sketch.pipeAt(path[^1]) == 'S'
|
||||||
|
|
||||||
|
let (left, right) = sketch.walkPath(path)
|
||||||
|
|
||||||
|
var candidates = sketch.cells
|
||||||
|
|
||||||
|
let pathSet = path.toHashSet
|
||||||
|
for p in pathSet:
|
||||||
|
candidates.excl p
|
||||||
|
|
||||||
|
var foundOutsideSide = false
|
||||||
|
var outside: Side
|
||||||
|
for p in sketch.floodFill((-1, -1), pathSet):
|
||||||
|
candidates.excl p
|
||||||
|
|
||||||
|
if p in pathSet:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Marked both ways, inconclusve?
|
||||||
|
if p in left and p in right:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if p in left:
|
||||||
|
if foundOutsideSide:
|
||||||
|
assert(outside == Left)
|
||||||
|
foundOutsideSide = true
|
||||||
|
outside = Left
|
||||||
|
elif p in right:
|
||||||
|
if foundOutsideSide:
|
||||||
|
assert(outside == Right)
|
||||||
|
foundOutsideSide = true
|
||||||
|
outside = Right
|
||||||
|
|
||||||
|
var inside: HashSet[Point]
|
||||||
|
for r in 0 ..< sketch.rows:
|
||||||
|
for c in 0 ..< sketch.cols:
|
||||||
|
let p = (r, c)
|
||||||
|
|
||||||
|
# Already known to be outside or on path
|
||||||
|
if p notin candidates:
|
||||||
|
continue
|
||||||
|
|
||||||
|
let s = whichSide(p, left, right)
|
||||||
|
if s.isSome and s.get == outside:
|
||||||
|
for pOut in sketch.floodFill(p, pathSet):
|
||||||
|
candidates.excl pOut
|
||||||
|
elif s.isSome and s.get != outside:
|
||||||
|
for pIn in sketch.floodFill(p, pathSet):
|
||||||
|
inside.incl pIn
|
||||||
|
else:
|
||||||
|
discard "Inconclusive! I hope this doesn't matter!"
|
||||||
|
|
||||||
|
assert(candidates.len == inside.len)
|
||||||
|
inside.len
|
||||||
|
|
||||||
echo part1()
|
echo part1()
|
||||||
|
echo part2()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue