-
Notifications
You must be signed in to change notification settings - Fork 0
/
bags.py
55 lines (45 loc) · 1.51 KB
/
bags.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import re
import click
import io
import networkx
import functools
from typing import Dict
def parse(rule: str) -> (str, Dict[str, int]):
match = re.compile(
r'^(((\w| )+) bags?) contain ((((\d+) ((\w| )+) bags?)(, ((\d+) ((\w| )+) bags?))*)|no other bags).$',
).match(rule)
if match is None:
raise ValueError('not a rule: {0}', rule)
parent = match.group(2)
subrules = match.group(5)
if subrules is None:
return parent, {}
children: Dict[str, int] = {}
for subrule in subrules.split(', '):
match = re.compile(
r'(\d+) ((\w| )+) bags?',
).match(subrule)
if match is None:
raise ValueError('not a rule part: {0}', subrule)
children[match.group(2)] = int(match.group(1))
return parent, children
def count_descends(graph: networkx.DiGraph, node: str) -> int:
total = 0
for child, _ in graph.in_edges(node):
count = 1 + count_descends(graph, child)
multiplier = graph.get_edge_data(child, node)['count']
total += multiplier * count
return total
@click.command()
@click.argument('rules', type=click.File('r'))
def main(rules: io.TextIOBase):
# Make a graph of bag membership
graph = networkx.DiGraph()
for rule in rules:
rule: str = rule.strip()
parent, children = parse(rule)
for child, count in children.items():
graph.add_edge(child, parent, count=count)
print(count_descends(graph, 'shiny gold'))
if __name__ == '__main__':
main()