2021-12-13 11:46:55 -06:00
|
|
|
import ./common, std/[sequtils, algorithm, sugar, sets, strformat, strutils, tables]
|
|
|
|
|
2021-12-14 13:53:51 -06:00
|
|
|
type
|
|
|
|
Lines = seq[string]
|
|
|
|
Path = seq[string]
|
|
|
|
NodeSet = HashSet[string]
|
|
|
|
Graph = Table[string, NodeSet]
|
|
|
|
|
|
|
|
proc toGraph(lines: Lines): Graph =
|
2021-12-13 14:47:22 -06:00
|
|
|
for l in lines:
|
2021-12-14 13:53:51 -06:00
|
|
|
let nodes = l.split "-"
|
|
|
|
if result.hasKey nodes[0]: result[nodes[0]].incl nodes[1]
|
|
|
|
else: result[nodes[0]] = toHashSet [nodes[1]]
|
|
|
|
if result.hasKey nodes[1]: result[nodes[1]].incl nodes[0]
|
|
|
|
else: result[nodes[1]] = toHashSet [nodes[0]]
|
|
|
|
|
|
|
|
proc basicIneligibleNodes(path: Path): NodeSet =
|
|
|
|
for n in path:
|
|
|
|
if n in ["start", "end"] or n.toUpper != n:
|
|
|
|
result.incl n
|
|
|
|
|
|
|
|
proc part2IneligibleNodes(path: Path): NodeSet =
|
|
|
|
# determine if we've double visited any small caves yet
|
|
|
|
var hasVisitedSingleSmallCaveTwice: bool = path.filterIt(not (it in ["start", "end"]) and it != it.toUpper).toCountTable.values.toSeq.anyIt(it >= 2)
|
|
|
|
for n in path:
|
|
|
|
let isStartOrEnd = n == "start" or n == "end"
|
|
|
|
let isSmall = n.toUpper != n
|
|
|
|
if isStartOrEnd or (isSmall and hasVisitedSingleSmallCaveTwice): result.incl n
|
|
|
|
|
|
|
|
# TODO: tail-call optimization?
|
|
|
|
proc visit(node: string, graph: Graph, ineligibleNodesReducer: (Path) -> NodeSet, path: Path = @[]): int =
|
|
|
|
if node == "end": return 1
|
|
|
|
let newPath = path & @[node]
|
|
|
|
let forbiddenNodes = newPath.ineligibleNodesReducer()
|
|
|
|
let toVisit = graph[node] - forbiddenNodes
|
|
|
|
for n in toVisit: result += visit(n, graph, ineligibleNodesReducer, newPath)
|
2021-12-13 11:46:55 -06:00
|
|
|
|
|
|
|
proc pathing(input: seq[string]): int =
|
2021-12-14 13:53:51 -06:00
|
|
|
visit("start", input.toGraph, basicIneligibleNodes)
|
2021-12-13 14:47:22 -06:00
|
|
|
|
|
|
|
proc pathing2(input: seq[string]): int =
|
2021-12-14 13:53:51 -06:00
|
|
|
visit("start", input.toGraph, part2IneligibleNodes)
|
2021-12-13 14:47:22 -06:00
|
|
|
|
|
|
|
const smallTestInput = @[
|
|
|
|
"start-A",
|
|
|
|
"start-b",
|
|
|
|
"A-c",
|
|
|
|
"A-b",
|
|
|
|
"b-d",
|
|
|
|
"A-end",
|
|
|
|
"b-end",
|
|
|
|
]
|
|
|
|
const stest = (smallTestInput, 10, 36)
|
2021-12-13 11:46:55 -06:00
|
|
|
|
2021-12-13 14:47:22 -06:00
|
|
|
# const largeTestInput = @[
|
|
|
|
# "fs-end",
|
|
|
|
# "he-DX",
|
|
|
|
# "fs-he",
|
|
|
|
# "start-DX",
|
|
|
|
# "pj-DX",
|
|
|
|
# "end-zg",
|
|
|
|
# "zg-sl",
|
|
|
|
# "zg-pj",
|
|
|
|
# "pj-he",
|
|
|
|
# "RW-he",
|
|
|
|
# "fs-DX",
|
|
|
|
# "pj-RW",
|
|
|
|
# "zg-RW",
|
|
|
|
# "start-pj",
|
|
|
|
# "he-WI",
|
|
|
|
# "zg-he",
|
|
|
|
# "pj-fs",
|
|
|
|
# "start-RW",
|
|
|
|
# ]
|
|
|
|
#
|
|
|
|
# const ltest = (largeTestInput, 225, 3509)
|
2021-12-13 11:46:55 -06:00
|
|
|
|
2021-12-13 14:47:22 -06:00
|
|
|
doDay 12, n => n.loadInput, pathing, pathing2, stest
|