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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy