Skip to content

Commit fd35573

Browse files
committed
added 2016/day11
1 parent d427205 commit fd35573

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

2016/day11/answers.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
47
2+
71

2016/day11/input.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The first floor contains a polonium generator, a thulium generator, a thulium-compatible microchip, a promethium generator, a ruthenium generator, a ruthenium-compatible microchip, a cobalt generator, and a cobalt-compatible microchip.
2+
The second floor contains a polonium-compatible microchip and a promethium-compatible microchip.
3+
The third floor contains nothing relevant.
4+
The fourth floor contains nothing relevant.

2016/day11/run.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#! /usr/bin/env python3
2+
3+
def load_data(filename):
4+
with open(filename, 'r') as f:
5+
for line in f:
6+
match = re.findall(r' a (?:(\w+)(?:-compatible)? (microchip|generator))(?:\.|,| and)', line)
7+
yield match
8+
9+
import re
10+
from typing import Callable
11+
from collections import namedtuple
12+
from collections.abc import Iterator
13+
from itertools import combinations
14+
import networkx as nx
15+
16+
type FS = frozenset[str]
17+
18+
FloorNT = namedtuple('FloorNT', ('generators', 'microchips'))
19+
20+
class Floor(FloorNT):
21+
generators: FS
22+
microships: FS
23+
24+
@classmethod
25+
def empty(cls) -> 'Floor':
26+
return cls(frozenset(), frozenset())
27+
28+
@classmethod
29+
def from_input(cls, items: list[tuple[str, str]]) -> 'Floor':
30+
microchips = set()
31+
generators = set()
32+
for element, type in items:
33+
if type == 'microchip':
34+
microchips.add(element)
35+
elif type == 'generator':
36+
generators.add(element)
37+
return cls(frozenset(generators), frozenset(microchips))
38+
39+
def is_valid(self) -> bool:
40+
return not self.generators or not (self.microchips - self.generators)
41+
42+
def to_input(self) -> list[tuple[str, str]]:
43+
return [ (element, 'generator') for element in self.generators ] + \
44+
[ (element, 'microchip') for element in self.microchips ]
45+
46+
def set_op(self, op: Callable[[FS, FS], FS], other: 'Floor') -> 'Floor':
47+
return Floor(op(self.generators, other.generators), op(self.microchips, other.microchips))
48+
49+
def __sub__(self, other: 'Floor') -> 'Floor':
50+
return self.set_op(frozenset.difference, other)
51+
52+
def __add__(self, other: 'Floor') -> 'Floor':
53+
return self.set_op(frozenset.union, other)
54+
55+
StateNT = namedtuple('StateNT', ('elevator_floor', 'floors'))
56+
57+
class State(StateNT):
58+
59+
elevator_floor: int
60+
floors: tuple['Floor']
61+
62+
@classmethod
63+
def from_input(cls, data: list) -> 'State':
64+
return cls(0, tuple( Floor.from_input(items) for items in data ) )
65+
66+
@classmethod
67+
def end(cls, s: 'State') -> 'State':
68+
full_floor = Floor.empty()
69+
for floor in s.floors:
70+
full_floor = full_floor + floor
71+
assert full_floor.generators == full_floor.microchips
72+
last_floor = len(s.floors)-1
73+
return cls(last_floor, tuple( Floor.empty() if i < last_floor else full_floor for i in range(last_floor+1) ))
74+
75+
def next_states(self) -> Iterator['State']:
76+
if self.elevator_floor > 0:
77+
# The elevator can go one floor down
78+
yield from self.next_states_floor(self.elevator_floor-1)
79+
if self.elevator_floor < len(self.floors)-1:
80+
# The elevator can go one floor up
81+
yield from self.next_states_floor(self.elevator_floor+1)
82+
83+
def next_states_floor(self, next_floor_n: int) -> Iterator['State']:
84+
this_floor = self.floors[self.elevator_floor]
85+
next_floor = self.floors[next_floor_n]
86+
87+
def try_next_state(next_floor_n, elevator):
88+
new_this_floor = this_floor - elevator
89+
new_next_floor = next_floor + elevator
90+
if new_this_floor.is_valid() and new_next_floor.is_valid():
91+
# So what
92+
yield State(next_floor_n, tuple( new_this_floor if floor is this_floor else new_next_floor if floor is next_floor else floor for floor in self.floors ))
93+
94+
items = this_floor.to_input()
95+
96+
# Try to pick one item
97+
for item in items:
98+
elevator = Floor.from_input([item])
99+
# Heuristic 1: don't take down microchips
100+
if next_floor_n < self.elevator_floor and item[1] == 'microchip':
101+
continue
102+
yield from try_next_state(next_floor_n, elevator)
103+
104+
# Heuristic 2: don't take down any pairs
105+
if next_floor_n < self.elevator_floor:
106+
return
107+
108+
# Try all possible pairs
109+
for item1, item2 in combinations(items, 2):
110+
elevator = Floor.from_input([item1, item2])
111+
yield from try_next_state(next_floor_n, elevator)
112+
113+
# Part One
114+
115+
def steps(input):
116+
117+
start_state = State.from_input(input)
118+
end_state = State.end(start_state)
119+
120+
G = nx.Graph()
121+
122+
to_check = [start_state]
123+
while to_check:
124+
state = to_check.pop(-1)
125+
for new_state in state.next_states():
126+
if new_state not in G.nodes:
127+
to_check.append(new_state)
128+
G.add_edge(state, new_state)
129+
130+
p = nx.shortest_path(G, start_state, end_state)
131+
132+
return len(p)-1
133+
134+
input = list(load_data("input.txt"))
135+
136+
print(steps(input))
137+
138+
# Part Two
139+
140+
input[0].extend([
141+
('elerium', 'generator'),
142+
('elerium', 'microchip'),
143+
('dilithium', 'generator'),
144+
('dilithium', 'microchip'),
145+
])
146+
147+
print(steps(input))
148+

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
```
22
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
33
2015 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ -- -- -- -- -- -- -- -- -- -- -- -- -- +
4-
2016 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
4+
2016 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ -- -- -- -- -- -- -- -- -- -- -- -- -- -
55
2017 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
66
2018 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ -- -- -- -- -- -- -- -- -- -- -- -- -- -- +
77
2019 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ -- ++ ++ -- +- ++ +- -

0 commit comments

Comments
 (0)