Skip to content

Commit e6bbc3c

Browse files
committed
Add days 19-25, 2016
1 parent ad33d1c commit e6bbc3c

File tree

7 files changed

+424
-0
lines changed

7 files changed

+424
-0
lines changed

2016/day19/solution.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from collections import deque
2+
3+
inp = 3017957
4+
5+
# Part 1
6+
def one_round(start, end, inc):
7+
n = (end - start + inc) // inc
8+
if n % 2 == 0:
9+
return (start, end - inc, 2*inc)
10+
else:
11+
return (start + 2*inc, end, 2*inc)
12+
13+
start, end, inc = 1, inp, 1
14+
while start + inc <= end:
15+
start, end, inc = one_round(start, end, inc)
16+
17+
print(start)
18+
19+
# Part 2
20+
left = deque(list(range(1, inp // 2 + 2)))
21+
right = deque(list(range(inp // 2 + 2, inp + 1)))
22+
23+
while len(left) + len(right) > 1:
24+
elf = left.popleft()
25+
if (len(left) + len(right)) % 2 == 1:
26+
right.popleft()
27+
else:
28+
left.pop()
29+
right.append(elf)
30+
left.append(right.popleft())
31+
32+
print(left[0])

2016/day20/solution.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
with open("input") as f:
2+
inp = f.read().strip()
3+
4+
ranges = [tuple(map(int, line.split("-"))) for line in inp.split("\n")]
5+
6+
# Part 1
7+
candidates = [0] + [end + 1 for _, end in ranges]
8+
9+
min_start = max(start for start, _ in ranges)
10+
for candidate in candidates:
11+
good = True
12+
for start, end in ranges:
13+
if start <= candidate and candidate <= end:
14+
good = False
15+
if good:
16+
min_start = min(min_start, candidate)
17+
18+
print(min_start)
19+
20+
# Part 2
21+
ticks = sorted([p for range in ranges for p in range])
22+
disj_ranges = set()
23+
for start, end in ranges:
24+
midps = [tick for tick in ticks if start < tick and tick < end]
25+
if not midps:
26+
disj_ranges.add((start, end))
27+
else:
28+
for p1, p2 in zip([start] + midps, midps + [end]):
29+
disj_ranges.add((p1, p2))
30+
31+
# Since the endpoints can be in multiple intervals we need to subtract how many times this occur.
32+
blacklist_length = sum(y - x + 1 for x, y in disj_ranges)
33+
endpoints = [p for range in disj_ranges for p in range]
34+
n_blacklist = blacklist_length - (len(endpoints) - len(set(endpoints)))
35+
print(2**32 - n_blacklist)

2016/day21/solution.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from itertools import permutations
2+
3+
with open("input") as f:
4+
inp = f.read().strip().split("\n")
5+
6+
7+
def swapposition(s, index1, index2):
8+
s[index1], s[index2] = s[index2], s[index1]
9+
return s
10+
11+
12+
def swapletter(s, letter1, letter2):
13+
return swapposition(s, s.index(letter1), s.index(letter2))
14+
15+
16+
def rotateleft(s, steps):
17+
assert steps <= len(s)
18+
s = s[steps:] + s[:steps]
19+
return s
20+
21+
22+
def rotateright(s, steps):
23+
s = rotateleft(s[::-1], steps)
24+
return s[::-1]
25+
26+
27+
def rotatebased(s, letter):
28+
steps = s.index(letter) + 1
29+
if steps >= 5:
30+
steps += 1
31+
steps %= len(s)
32+
return rotateright(s, steps)
33+
34+
35+
def reversepositions(s, index1, index2):
36+
sub_s = s[index1:(index2 + 1)]
37+
return s[:index1] + sub_s[::-1] + s[(index2 + 1):]
38+
39+
40+
def moveposition(s, index1, index2):
41+
s.insert(index2, s.pop(index1))
42+
return s
43+
44+
45+
def scramble(s):
46+
s = list(s)
47+
for line in inp:
48+
parts = line.split(" ")
49+
operation = parts[0] + parts[1]
50+
match operation:
51+
case "swapposition":
52+
s = swapposition(s, int(parts[2]), int(parts[5]))
53+
case "swapletter":
54+
s = swapletter(s, parts[2], parts[5])
55+
case "rotateleft":
56+
s = rotateleft(s, int(parts[2]))
57+
case "rotateright":
58+
s = rotateright(s, int(parts[2]))
59+
case "rotatebased":
60+
s = rotatebased(s, parts[6])
61+
case "reversepositions":
62+
s = reversepositions(s, int(parts[2]), int(parts[4]))
63+
case "moveposition":
64+
s = moveposition(s, int(parts[2]), int(parts[5]))
65+
return "".join(s)
66+
67+
# Part 1
68+
pw = "abcdefgh"
69+
print(scramble(pw))
70+
71+
# Part 2
72+
for s in permutations(pw, len(pw)):
73+
s = "".join(s)
74+
if scramble(s) == "fbgdceah":
75+
print(s)
76+
break

2016/day22/solution.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import re
2+
from itertools import permutations
3+
4+
with open("input") as f:
5+
inp = f.read().strip().split("\n")
6+
7+
nodes = []
8+
for line in inp:
9+
if line[0] != "/":
10+
continue
11+
nodes.append(tuple(map(int, re.findall("\d+", line))))
12+
13+
# Part 1
14+
viable_pairs = 0
15+
for nodea, nodeb in permutations(nodes, 2):
16+
_, _, _, useda, availa, _ = nodea
17+
_, _, _, usedb, availb, _ = nodeb
18+
if useda > 0 and useda <= availb:
19+
viable_pairs += 1
20+
21+
print(viable_pairs)
22+
23+
# Part 2
24+
25+
# Our grid is 38x38 so the node we have access to is at (0,0) and the node which data we want to access is at (37,0).
26+
# Data blocks between (12,6) and (12,37) are large (490-498), and they can never fit into smaller sized nodes,
27+
# so they're stuck. This means we can only move data between smaller sized nodes (85-94). Since the smaller data blocks
28+
# themselves are between 64-73 these can only be moved into an empty node. We note that all small-sized data blocks can fit
29+
# into all nodes.
30+
# Since there is only one empty node, we are essentially swapping data between the empty node and its neighbors.
31+
#
32+
# Our grid looks like this:
33+
#
34+
# a . . . . . . . . . . . . d
35+
# . . . . . . . . . . . . . .
36+
# . . . . . . . . . . . . . .
37+
# . . . . # # # # # # # # # #
38+
# . . . . . . . . . . . . . .
39+
# . . . . . . . . . . . . . .
40+
# . . . . . . . . . . . 0 . .
41+
#
42+
# where
43+
# - # denotes the large nodes which can't be moved (12,6), ..., (12,37)
44+
# - d denotes the data we want access to (37,0)
45+
# - a the node we have access to (0,0)
46+
# - 0 the empty node (16,23)
47+
#
48+
# The goal is to move d to a. This can be achieved in two steps:
49+
# 1. Move the empty node up next to d, so the grid looks like this:
50+
#
51+
# a . . . . . . . . . . . 0 d
52+
# . . . . . . . . . . . . . .
53+
# . . . . . . . . . . . . . .
54+
# . . . . # # # # # # # # # #
55+
# . . . . . . . . . . . . . .
56+
# . . . . . . . . . . . . . .
57+
# . . . . . . . . . . . . . .
58+
#
59+
# This can be done via this path: (16,23) -> (5,23) -> (5,0) -> (36,0) which
60+
# takes (16 - 5) + (23 - 0) + (36 - 5) steps
61+
#
62+
# 2. Then we move d to left one step and then we need to move the 0 around d so it
63+
# again is in front requiring 4 steps. This would look like this:
64+
#
65+
# a . . . . . . . . . . . d 0
66+
# . . . . . . . . . . . . . .
67+
# . . . . . . . . . . . . . .
68+
# . . . . # # # # # # # # # #
69+
# . . . . . . . . . . . . . .
70+
# . . . . . . . . . . . . . .
71+
# . . . . . . . . . . . . . .
72+
#
73+
# a . . . . . . . . . . 0 d .
74+
# . . . . . . . . . . . . . .
75+
# . . . . . . . . . . . . . .
76+
# . . . . # # # # # # # # # #
77+
# . . . . . . . . . . . . . .
78+
# . . . . . . . . . . . . . .
79+
# . . . . . . . . . . . . . .
80+
#
81+
# Each step to the left requires 5 steps and we end up with 0 in front af d. After 36*5 steps
82+
# d is positioned at (1,0) with 0 at (0,0). After one additional step d has been moved to (0,0).
83+
print((16 - 5) + (23 - 0) + (36 - 5) + 36*5 + 1)

2016/day23/solution.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
with open("input") as f:
2+
inp = f.read().strip()
3+
4+
instr_init = []
5+
for line in inp.split("\n"):
6+
op, *rest = line.split(" ")
7+
instr_init.append((op, rest))
8+
9+
# After inspecting the input, we note that the sequence:
10+
#
11+
# inc a
12+
# dec c
13+
# jnz c -2
14+
# dec d
15+
# jnz d -5
16+
#
17+
# will add c*d to a and reset c and d to 0. We replace this with a
18+
# new operation caled mlt x y which multiplies registers x and y
19+
# and adds the result to register a:
20+
#
21+
# mlt c d
22+
# cpy 0 c
23+
# cpy 0 d
24+
# cpy 0 c
25+
# cpy 0 d
26+
#
27+
# The reason we have the two cpy instructions twice is to keep the
28+
# instructions at the same length.
29+
30+
instr_init[5:10] = [
31+
("mlt", ["c", "d"]),
32+
("cpy", ["0", "c"]),
33+
("cpy", ["0", "d"]),
34+
("cpy", ["0", "c"]),
35+
("cpy", ["0", "d"])
36+
]
37+
38+
39+
def solve(a):
40+
register = {x: 0 for x in ("a", "b", "c", "d")}
41+
register["a"] = a
42+
pointer = 0
43+
instr = list(instr_init)
44+
while pointer < len(instr):
45+
op, rest = instr[pointer]
46+
if op == "cpy":
47+
val, target = rest
48+
register[target] = int(val) if val.lstrip("-").isnumeric() else int(register[val])
49+
elif op == "inc":
50+
register[rest[0]] += 1
51+
elif op == "dec":
52+
register[rest[0]] -= 1
53+
elif op == "jnz":
54+
x, y = rest
55+
decider = int(x) if x.isnumeric() else register[x]
56+
y = int(y) if y.lstrip("-").isnumeric() else register[y]
57+
if decider != 0:
58+
pointer += y
59+
continue
60+
elif op == "tgl":
61+
t_pointer = pointer + register[rest[0]]
62+
if t_pointer < len(instr):
63+
t_op, t_rest = instr[t_pointer]
64+
if t_op == "inc":
65+
instr[t_pointer] = ("dec", t_rest)
66+
elif t_op == "dec":
67+
instr[t_pointer] = ("inc", t_rest)
68+
elif t_op == "tgl":
69+
instr[t_pointer] = ("inc", t_rest)
70+
elif t_op == "jnz":
71+
instr[t_pointer] = ("cpy", t_rest)
72+
elif t_op == "cpy":
73+
instr[t_pointer] = ("jnz", t_rest)
74+
elif op == "mlt":
75+
register["a"] += register[rest[0]]*register[rest[1]]
76+
pointer += 1
77+
return register["a"]
78+
79+
80+
# Part 1
81+
print(solve(7))
82+
83+
# Part 2
84+
print(solve(12))

2016/day24/solution.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from collections import deque
2+
import networkx as nx
3+
from itertools import permutations
4+
5+
with open("input") as f:
6+
inp = f.read().strip().split("\n")
7+
8+
nums = {j + i*1j: int(x) for i, l in enumerate(inp) for j, x in enumerate(l) if x.isdigit()}
9+
rev_nums = {num: p for p, num in nums.items()}
10+
opens = {j + i*1j for i, l in enumerate(inp) for j, x in enumerate(l) if x != "#"}
11+
12+
13+
def get_distances(num):
14+
res = {}
15+
seen = set()
16+
q = deque([(rev_nums[num], 0)])
17+
other_nums = set(p for p, num2 in nums.items() if num2 != num)
18+
while q:
19+
p, steps = q.popleft()
20+
if p in other_nums:
21+
if nums[p] not in res:
22+
res[nums[p]] = steps
23+
res[nums[p]] = min(steps, res[nums[p]])
24+
continue
25+
if p in seen:
26+
continue
27+
seen.add(p)
28+
for dp in (1, -1, 1j, -1j):
29+
new_p = p + dp
30+
if new_p not in opens:
31+
continue
32+
q.append((new_p, steps + 1))
33+
return res
34+
35+
36+
g = nx.Graph()
37+
for num in range(8):
38+
for other_num, steps in get_distances(num).items():
39+
g.add_edge(num, other_num, weight = steps)
40+
41+
42+
def steps(route):
43+
res = 0
44+
for n1, n2 in zip([0] + route, route):
45+
if (n1, n2) not in g.edges():
46+
return None
47+
res += g[n1][n2]["weight"]
48+
return res
49+
50+
51+
paths = []
52+
for route in permutations(range(1, 8), 7):
53+
route = list(route)
54+
res = steps(route)
55+
if res is not None:
56+
paths.append((res, g[route[-1]][0]["weight"]))
57+
58+
# Part 1
59+
print(min(path for path, _ in paths))
60+
61+
# Part 2
62+
print(min(path + return_ for path, return_ in paths))

0 commit comments

Comments
 (0)