191 lines
4.1 KiB
Nim
191 lines
4.1 KiB
Nim
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)
|
|
|
|
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 Platform = object
|
|
height: int
|
|
width: int
|
|
|
|
rows: seq[seq[char]]
|
|
cols: seq[seq[char]]
|
|
|
|
func parsePlatform(text: string): Platform =
|
|
let lines = text.strip.splitLines()
|
|
result.height = lines.len
|
|
result.width = lines[0].len
|
|
|
|
for r in 0 ..< result.height:
|
|
result.rows.add @[]
|
|
|
|
for c in 0 ..< result.width:
|
|
result.cols.add @[]
|
|
|
|
for (r, line) in enumerate(lines):
|
|
for (c, sym) in enumerate(line):
|
|
result.rows[r] &= @[sym]
|
|
result.cols[c] &= @[sym]
|
|
|
|
func render(p: Platform): string =
|
|
p.rows.mapIt(it.mapIt($it).join("")).join("\n")
|
|
|
|
proc fromCols(cols: seq[seq[char]]): Platform =
|
|
result.cols = cols
|
|
result.width = cols.len
|
|
result.height = cols[0].len
|
|
|
|
for r in 0 ..< result.height:
|
|
result.rows.add @[]
|
|
|
|
for (c, col) in enumerate(cols):
|
|
for (r, sym) in enumerate(col):
|
|
result.rows[r] &= @[sym]
|
|
|
|
proc fromRows(rows: seq[seq[char]]): Platform =
|
|
result.rows = rows
|
|
result.height = rows.len
|
|
result.width = rows[0].len
|
|
|
|
for c in 0 ..< result.width:
|
|
result.cols.add @[]
|
|
|
|
for (r, row) in enumerate(rows):
|
|
for (c, sym) in enumerate(row):
|
|
result.cols[c] &= @[sym]
|
|
|
|
func tilt(rocks: seq[char], order: SortOrder): seq[char] =
|
|
let parts = rocks.split(func (sym: char): bool = sym == '#')
|
|
var rolled: seq[seq[char]]
|
|
for part in parts:
|
|
rolled.add part.sorted(order)
|
|
result = rolled.join('#')
|
|
|
|
func tiltCols(p: Platform, order: SortOrder): Platform =
|
|
var cols: seq[seq[char]]
|
|
for col in p.cols:
|
|
cols.add col.tilt(order)
|
|
fromCols(cols)
|
|
|
|
func tiltRows(p: Platform, order: SortOrder): Platform =
|
|
var rows: seq[seq[char]]
|
|
for row in p.rows:
|
|
rows.add row.tilt(order)
|
|
fromRows(rows)
|
|
|
|
func tiltNorth(p: Platform): Platform =
|
|
p.tiltCols(Descending)
|
|
|
|
func tiltSouth(p: Platform): Platform =
|
|
p.tiltCols(Ascending)
|
|
|
|
func tiltWest(p: Platform): Platform =
|
|
p.tiltRows(Descending)
|
|
|
|
func tiltEast(p: Platform): Platform =
|
|
p.tiltRows(Ascending)
|
|
|
|
type Direction = enum
|
|
N
|
|
S
|
|
E
|
|
W
|
|
|
|
func tilt(p: Platform, d: Direction): Platform =
|
|
case d:
|
|
of N: p.tiltNorth
|
|
of S: p.tiltSouth
|
|
of E: p.tiltEast
|
|
of W: p.tiltWest
|
|
|
|
proc loadNorth(platform: Platform): int =
|
|
for (r, row) in enumerate(platform.rows):
|
|
for sym in row:
|
|
if sym != 'O': continue
|
|
result.inc (platform.height - r)
|
|
|
|
proc part1(input: string): int =
|
|
let text = readFile(input)
|
|
let platform = parsePlatform(text)
|
|
platform.tiltNorth.loadNorth
|
|
|
|
func cycle(p: Platform): Platform =
|
|
p.tiltNorth.tiltWest.tiltSouth.tiltEast
|
|
|
|
type Loop = tuple
|
|
start: int
|
|
stop: int
|
|
length: int
|
|
|
|
proc findLoop(p: Platform): (Loop, Platform) =
|
|
let cycle = @[N, W, S, E]
|
|
|
|
var seen: Table[Platform, int]
|
|
seen[p] = 0
|
|
|
|
var p = p
|
|
var i = 0
|
|
while true:
|
|
inc i
|
|
|
|
for dir in cycle:
|
|
p = p.tilt(dir)
|
|
|
|
if p in seen:
|
|
let start = seen[p]
|
|
return ((start, i, i - start), p)
|
|
seen[p] = i
|
|
|
|
proc part2(input: string): int =
|
|
let text = readFile(input)
|
|
let platform = parsePlatform(text)
|
|
let cycles = 1_000_000_000
|
|
|
|
let (loop, state) = platform.findLoop
|
|
|
|
let repeats = (cycles - loop.stop).floorDiv(loop.length)
|
|
let timeSkip = loop.stop + repeats * loop.length
|
|
let remaining = cycles - timeSkip
|
|
|
|
var p = state
|
|
for _ in 0 ..< remaining:
|
|
p = p.cycle
|
|
p.loadNorth
|
|
|
|
echo part1("input/test.txt")
|
|
echo part1("input/day14.txt")
|
|
echo part2("input/test.txt")
|
|
echo part2("input/day14.txt")
|