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/enumerate
|
||||
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
|
||||
|
||||
proc dbg[T](v: T): T = (echo(v); v)
|
||||
|
||||
|
|
@ -33,8 +35,11 @@ func parseSketch(text: string): Sketch =
|
|||
if sym == 'S':
|
||||
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 =
|
||||
if p.r in 0 ..< s.rows and p.c in 0 ..< s.cols:
|
||||
if s.inBounds(p):
|
||||
s.grid[p]
|
||||
else:
|
||||
'.'
|
||||
|
|
@ -65,6 +70,8 @@ func neighbors(s: Sketch, p: Point): seq[Point] =
|
|||
(0, +1, E, W), # Right E-W
|
||||
]:
|
||||
let n = (p.r + dr, p.c + dc)
|
||||
if not s.inBounds(n):
|
||||
continue
|
||||
if s.hasConn(p, pDir) and s.hasConn(n, nDir):
|
||||
result.add(n)
|
||||
|
||||
|
|
@ -93,7 +100,190 @@ proc part1(): int =
|
|||
let text = readFile("input/day10.txt")
|
||||
let sketch = parseSketch(text)
|
||||
|
||||
for d in values(sketch.reachable):
|
||||
let reachable = sketch.reachable
|
||||
for d in values(reachable):
|
||||
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 part2()
|
||||
|
|
|
|||
Loading…
Reference in a new issue