2017-12-07 - Recursive Circus
(original .ipynb)
test_puzzle_input = """pbga (66)
xhth (57)
ebii (61)
havc (66)
ktlj (57)
fwft (72) -> ktlj, cntj, xhth
qoyq (66)
padx (45) -> pbga, havc, qoyq
tknk (41) -> ugml, padx, fwft
jptl (61)
ugml (68) -> gyxo, ebii, jptl
gyxo (61)
cntj (57)"""
real_puzzle_input = open("puzzle_input/day7.txt").read()
def parse_weight_str(weight_str):
return int(weight_str[1:][:-1])
def parse_line(line):
line_parts = line.split(" -> ")
name_and_weight = line_parts[0]
name, weight_str = name_and_weight.split(" ")
weight = parse_weight_str(weight_str)
children = None
if len(line_parts) == 2:
children = [ child.strip() for child in line_parts[1].split(",") ]
return (name, weight, children)
def build_parents_tree(puzzle):
parent_of = {}
for node, weight, children in puzzle:
if children:
for child in children:
parent_of[child] = node
return parent_of
def find_root(start_node, parent_of):
current = start_node
while current != None:
if not (current in parent_of):
return current
current = parent_of[current]
raise Exception("Something has gone seriously wrong")
def part_one(puzzle_input):
puzzle = [parse_line(line_str) for line_str in puzzle_input.split("\n")]
parent_of = build_parents_tree(puzzle)
start_node = list(parent_of.keys())[0]
return find_root(start_node, parent_of)
# parse_line("fwft (72) -> ktlj, cntj, xhth")
# parse_line("gyxo (61)")
print("part one:", part_one(real_puzzle_input))
part one: eqgvf
def build_main_tree(puzzle):
children_of = {}
for node, _, children in puzzle:
children_of[node] = children
return children_of
def build_weight_tree(puzzle):
weight_of = {}
for node, weight, _ in puzzle:
weight_of[node] = weight
return weight_of
def build_total_weights(node, total_weights, children_of, weight_of):
total = weight_of[node]
if children_of[node]:
for child in children_of[node]:
total += build_total_weights(child, total_weights, children_of, weight_of)
total_weights[node] = total
return total
def odd_one_out(node, total_weight_of, children_of):
children = children_of[node]
if not children:
return node
if len(set([total_weight_of[child] for child in children])) == 1:
return node
children_and_weights = list(
sorted([ (child, total_weight_of[child]) for child in children ], key=lambda x:x[1])
)
if children_and_weights[0][1] != children_and_weights[1][1]:
# if first is different from second, it's the odd one out
return odd_one_out(children_and_weights[0][0], total_weight_of, children_of)
else:
# otherwise the last one will be
return odd_one_out(children_and_weights[-1][0], total_weight_of, children_of)
def part_two(puzzle_input):
puzzle = [parse_line(line_str) for line_str in puzzle_input.split("\n")]
parent_of = build_parents_tree(puzzle)
children_of = build_main_tree(puzzle)
weight_of = build_weight_tree(puzzle)
start_node = list(parent_of.keys())[0]
root = find_root(start_node, parent_of)
total_weight_of = {}
build_total_weights(root, total_weight_of, children_of, weight_of)
weirdo = odd_one_out(root, total_weight_of, children_of)
target_weight = [total_weight_of[child] for child in children_of[parent_of[weirdo]] if total_weight_of[child] != total_weight_of[weirdo]][0]
return weight_of[weirdo] + (target_weight - total_weight_of[weirdo])
print("part two:", part_two(real_puzzle_input))
part two: 757