175 lines
3.8 KiB
Nim
175 lines
3.8 KiB
Nim
import std/enumerate
|
|
import std/math
|
|
import std/sequtils
|
|
import std/sets
|
|
import std/strscans
|
|
import std/strutils
|
|
import std/sugar
|
|
import std/tables
|
|
|
|
type Remap = tuple
|
|
dst_start: int
|
|
src_start: int
|
|
length: int
|
|
|
|
func parseRemap(s: string): Remap =
|
|
let parts = s.splitWhitespace.map(parseInt)
|
|
(parts[0], parts[1], parts[2])
|
|
|
|
func includes(r: Remap, src: int): bool =
|
|
let range = r.src_start ..< (r.src_start + r.length)
|
|
src in range
|
|
|
|
func lookup(r: Remap, src: int): int =
|
|
if r.includes(src):
|
|
return r.dst_start + (src - r.src_start)
|
|
src
|
|
|
|
type Map = object
|
|
src: string
|
|
dst: string
|
|
remaps: seq[Remap]
|
|
|
|
func parseMap(s: string): Map =
|
|
let lines = s.strip.splitLines
|
|
if not scanf(lines[0], "$+-to-$+ map:$.", result.src, result.dst):
|
|
raise newException(ValueError, lines[0])
|
|
|
|
for line in lines[1..^1]:
|
|
result.remaps.add parseRemap(line)
|
|
|
|
func lookup(m: Map, src: int): int =
|
|
for remap in m.remaps:
|
|
if remap.includes(src):
|
|
return remap.lookup(src)
|
|
src
|
|
|
|
type Almanac = object
|
|
seeds: seq[int]
|
|
mapsBySrc: Table[string, Map]
|
|
mapsByDst: Table[string, Map]
|
|
|
|
func parseAlmanac(s: string): Almanac =
|
|
let sections = s.strip.split("\n\n")
|
|
|
|
var seeds: string
|
|
if not scanf(sections[0], "seeds: $*$.", seeds):
|
|
raise newException(ValueError, sections[0])
|
|
|
|
for num in seeds.strip.splitWhitespace:
|
|
result.seeds.add num.parseInt
|
|
|
|
for section in sections[1..^1]:
|
|
let map = parseMap(section)
|
|
result.mapsBySrc[map.src] = map
|
|
result.mapsByDst[map.dst] = map
|
|
|
|
func location(a: Almanac, seed: int): int =
|
|
var map = a.mapsBySrc["seed"]
|
|
var src = seed
|
|
|
|
while true:
|
|
src = map.lookup(src)
|
|
if map.dst == "location":
|
|
return src
|
|
|
|
map = a.mapsBySrc[map.dst]
|
|
|
|
proc part1(): int =
|
|
let text = readFile("input/day05.txt")
|
|
let almanac = parseAlmanac(text)
|
|
|
|
let locations = collect(newSeq):
|
|
for seed in almanac.seeds:
|
|
almanac.location(seed)
|
|
|
|
locations.min
|
|
|
|
type Range = tuple
|
|
lo: int
|
|
hi: int
|
|
|
|
func newRange(start: int, length: int): Range =
|
|
(start, start + length - 1)
|
|
|
|
func empty(r: Range): bool =
|
|
r.lo > r.hi
|
|
|
|
type Split = tuple
|
|
prefix: Range
|
|
inside: Range
|
|
suffix: Range
|
|
|
|
func split(a: Range, b: Range): Split =
|
|
let lo = max(a.lo, b.lo)
|
|
let hi = min(a.hi, b.hi)
|
|
|
|
result.prefix = (a.lo, lo - 1)
|
|
result.inside = (lo, hi)
|
|
result.suffix = (hi + 1, a.hi)
|
|
|
|
func lookup(r: Remap, src: Range): Range =
|
|
let delta = r.dst_start - r.src_start
|
|
(src.lo + delta, src.hi + delta)
|
|
|
|
func lookup(m: Map, src: Range): seq[Range] =
|
|
var unmapped = @[src]
|
|
|
|
for remap in m.remaps:
|
|
var still_unmapped: seq[Range]
|
|
for src in unmapped:
|
|
let rr = newRange(remap.src_start, remap.length)
|
|
let split = src.split(rr)
|
|
|
|
if split.inside.empty:
|
|
still_unmapped.add src
|
|
continue
|
|
|
|
result.add remap.lookup(split.inside)
|
|
|
|
if not split.prefix.empty:
|
|
still_unmapped.add split.prefix
|
|
|
|
if not split.suffix.empty:
|
|
still_unmapped.add split.suffix
|
|
|
|
unmapped = still_unmapped
|
|
|
|
for src in unmapped:
|
|
result.add src
|
|
|
|
func lookup(m: Map, srcs: seq[Range]): seq[Range] =
|
|
for src in srcs:
|
|
result.add m.lookup(src)
|
|
|
|
func lookup(a: Almanac, dst: string, seeds: seq[Range]): seq[Range] =
|
|
var map = a.mapsBySrc["seed"]
|
|
var src = seeds
|
|
|
|
while true:
|
|
src = map.lookup(src)
|
|
if map.dst == dst:
|
|
return src
|
|
|
|
map = a.mapsBySrc[map.dst]
|
|
|
|
proc part2(): int =
|
|
let text = readFile("input/day05.txt")
|
|
let almanac = parseAlmanac(text)
|
|
|
|
var ranges: seq[Range]
|
|
|
|
for i in 0 ..< floorDiv(almanac.seeds.len, 2):
|
|
let start = almanac.seeds[2*i]
|
|
let len = almanac.seeds[2*i + 1]
|
|
ranges.add newRange(start, len)
|
|
|
|
let locations = collect(newSeq):
|
|
for locs in almanac.lookup("location", ranges):
|
|
if not locs.empty:
|
|
locs.lo
|
|
|
|
locations.min
|
|
|
|
echo part1()
|
|
echo part2()
|