2016: d10: ex1: add solution
This commit is contained in:
parent
5cc92903db
commit
840d9d5b67
1 changed files with 110 additions and 0 deletions
110
2016/d10/ex1/ex1.py
Executable file
110
2016/d10/ex1/ex1.py
Executable file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import dataclasses
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class Input:
|
||||||
|
n: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class Bot:
|
||||||
|
n: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class Output:
|
||||||
|
n: int
|
||||||
|
|
||||||
|
|
||||||
|
# Each node points to its children, to each of whom it outputs a chip
|
||||||
|
GraphKey = Input | Bot
|
||||||
|
GraphVal = Bot | Output
|
||||||
|
# By convention, a bot should list its outputs in [`low`, `high`] order
|
||||||
|
Graph = dict[GraphKey, list[GraphVal]]
|
||||||
|
# Reverse the graph representation for an easier topo_sort (only of the keys)
|
||||||
|
ReverseGraph = dict[GraphKey, set[GraphKey]]
|
||||||
|
|
||||||
|
|
||||||
|
def solve(input: str) -> int:
|
||||||
|
def parse_line(input: str) -> tuple[GraphKey, list[GraphVal]]:
|
||||||
|
split_input = input.split()
|
||||||
|
if split_input[0] == "bot":
|
||||||
|
low_n = int(split_input[6])
|
||||||
|
low_type: type[GraphVal] = Bot if split_input[5] == "bot" else Output
|
||||||
|
high_n = int(split_input[11])
|
||||||
|
high_type: type[GraphVal] = Bot if split_input[10] == "bot" else Output
|
||||||
|
return Bot(int(split_input[1])), [low_type(low_n), high_type(high_n)]
|
||||||
|
return Input(int(split_input[1])), [Bot(int(split_input[-1]))]
|
||||||
|
|
||||||
|
def parse(input: str) -> Graph:
|
||||||
|
return {key: val for key, val in map(parse_line, input.splitlines())}
|
||||||
|
|
||||||
|
def run(graph: Graph) -> int:
|
||||||
|
def reverse_graph(graph: Graph) -> ReverseGraph:
|
||||||
|
res: ReverseGraph = {n: set() for n in graph}
|
||||||
|
for node, children in graph.items():
|
||||||
|
for child in children:
|
||||||
|
# We don't care about `Output`s here
|
||||||
|
if isinstance(child, Output):
|
||||||
|
continue
|
||||||
|
res[child].add(node)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def topo_sort(graph: ReverseGraph) -> list[GraphKey]:
|
||||||
|
res: list[GraphKey] = []
|
||||||
|
|
||||||
|
queue = {n for n, deps in graph.items() if not deps}
|
||||||
|
assert all(isinstance(n, Input) for n in queue) # Sanity check
|
||||||
|
seen: set[GraphKey] = set()
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
node = queue.pop()
|
||||||
|
|
||||||
|
res.append(node)
|
||||||
|
seen.add(node)
|
||||||
|
# Iterate over all nodes as we don't have information on children
|
||||||
|
for child, deps in graph.items():
|
||||||
|
if child in seen:
|
||||||
|
continue
|
||||||
|
if deps - seen:
|
||||||
|
continue
|
||||||
|
queue.add(child)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
reversed_graph = reverse_graph(graph)
|
||||||
|
assert len(reversed_graph) == len(graph) # Sanity check
|
||||||
|
run_order = topo_sort(reversed_graph)
|
||||||
|
assert len(run_order) == len(graph) # Sanity check
|
||||||
|
CHIPS_OF_INTEREST = {17, 61}
|
||||||
|
bots_bins: dict[GraphVal, list[int]] = collections.defaultdict(list)
|
||||||
|
for node in run_order:
|
||||||
|
match node:
|
||||||
|
case Input(n):
|
||||||
|
assert len(graph[node]) == 1 # Sanity check
|
||||||
|
bots_bins[graph[node][0]].append(n)
|
||||||
|
case Bot(n):
|
||||||
|
assert len(graph[node]) == 2 # Sanity check
|
||||||
|
assert len(bots_bins[node]) == 2 # Sanity check
|
||||||
|
# Have we found the bot we were looking for?
|
||||||
|
if set(bots_bins[node]) == CHIPS_OF_INTEREST:
|
||||||
|
return n
|
||||||
|
for out, val in zip(graph[node], sorted(bots_bins[node])):
|
||||||
|
bots_bins[out].append(val)
|
||||||
|
assert False # Sanity check
|
||||||
|
|
||||||
|
graph = parse(input)
|
||||||
|
return run(graph)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
input = sys.stdin.read()
|
||||||
|
print(solve(input))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue