2017 Advent of Code (Python)

By telleropnul, April 8, 2023

Advent of Code is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like. Go check it out: Advent of Code

Input files: adventofcode2017inputs.zip [To be uploaded still…]

2017 Day 25 Part 02

1
 

2017 Day 25 Part 01

1
 

2017 Day 24 Part 02

1
 

2017 Day 24 Part 01

1
 

2017 Day 23 Part 02

1
 

2017 Day 23 Part 01

1
 

2017 Day 22 Part 02

1
 

2017 Day 22 Part 01

1
 

2017 Day 21 Part 02

1
 

2017 Day 21 Part 01

1
 

2017 Day 20 Part 02

1
 

2017 Day 20 Part 01

1
 

2017 Day 19

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
instructions = open('input').read().splitlines()
 
pos = instructions[0].find('|') + 0j
direction = 1j
letters = []
steps = 0
 
 
get_char = lambda pos: instructions[int(pos.imag)][int(pos.real)]
 
def change_direction(direction):
    candidate = direction * 1j
    return candidate if get_char(pos + candidate) != ' ' else direction * -1j
 
 
char = get_char(pos)
while char != ' ':
    steps += 1
    if char.isalpha():
        letters.append(char)
    elif char == '+':
        direction = change_direction(direction)
    pos += direction
    char = get_char(pos)
 
print(''.join(letters))
print(steps)

2017 Day 18

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
56
57
58
59
60
61
62
63
64
65
from collections import defaultdict, deque
 
instructions = open('input').read().splitlines()
interpret = lambda val, reg: reg[val] if val.isalpha() else int(val)
sent_counter = 0
 
 
def solve(registers, which, i=0, first_part=False):
    played = 0
    while i < len(instructions):
        c = instructions[i].split()
        v = interpret(c[-1], registers)
        if c[0] == 'set':
            registers[c[1]] = v
        elif c[0] == 'add':
            registers[c[1]] += v
        elif c[0] == 'mul':
            registers[c[1]] *= v
        elif c[0] == 'mod':
            registers[c[1]] %= v
        elif c[0] == 'jgz':
            w = interpret(c[1], registers)
            if w > 0:
                i += v
                continue
        elif c[0] == 'snd':
            if first_part:
                played = v
 
            elif which == 'x':
                y.append(v)
            else:
                global sent_counter
                sent_counter += 1
                x.append(v)
        elif c[0] == 'rcv':
            if first_part:
                if v != 0:
                    return played
 
            elif which == 'x' and x:
                registers[c[-1]] = x.popleft()
            elif which == 'y' and y:
                registers[c[-1]] = y.popleft()
            else:
                return i
        i += 1
 
 
print(solve(defaultdict(int), 'x', first_part=True))
 
registers_x = defaultdict(int)
registers_y = defaultdict(int)
registers_y['p'] = 1
 
x = deque([])
y = deque([])
 
x_i = solve(registers_x, 'x')
y_i = solve(registers_y, 'y')
while x or y:
    x_i = solve(registers_x, 'x', x_i)
    y_i = solve(registers_y, 'y', y_i)
 
print(sent_counter)

2017 Day 17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from collections import deque
 
def spin(insertions):
    spinlock = deque([0])
    for i in range(1, insertions+1):
        spinlock.rotate(-puzzle)
        spinlock.append(i)
    return spinlock
 
 
puzzle = 337
ans = spin(2017)
print(ans[0])
 
ans = spin(50_000_000)
print(ans[ans.index(0) + 1])

2017 Day 16

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
from collections import deque
 
programs = 'abcdefghijklmnop'
 
with open('input') as f:
    instructions = f.readline().split(',')
 
 
def clean_up(instructions):
    result = []
    for instr in instructions:
        first = instr[0]
        if first == 's':
            result.append((first, int(instr[1:])))
        elif first == 'x':
            a, b = map(int, instr[1:].split('/'))
            result.append((first, a, b))
        else:
            result.append((first, instr[1], instr[-1]))
    return result
 
 
def dance(dancers):
    dancers = deque(dancers)
    for first, *rest in cleaned:
        if first == 's':
            dancers.rotate(rest[0])
        elif first == 'x':
            a, b = rest
            dancers[a], dancers[b] = dancers[b], dancers[a]
        elif first == 'p':
            x, y = rest
            a = dancers.index(x)
            b = dancers.index(y)
            dancers[a], dancers[b] = y, x
    return ''.join(dancers)
 
 
def long_dance(dancers, iterations=1_000_000_000):
    seen = [dancers]
    for i in range(1, iterations):
        dancers = dance(dancers)
        if dancers == programs:
            return seen[iterations % i]
        seen.append(dancers)
 
 
cleaned = clean_up(instructions)
print(dance(programs))
print(long_dance(programs))

2017 Day 15

Please be patient…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
data = open('input').read().splitlines()
data = [line.split(' ') for line in data]
a = int(data[0][4])
b = int(data[1][4])
 
factors = {'a': 16807, 'b': 48271}
divisor = 2147483647
 
def generator(value, factor, multi=1):
    while True:
        value = value * factor % divisor
        if value % multi == 0:
            yield value & 0xFFFF
 
 
gen_a = generator(a, factors['a'])
gen_b = generator(b, factors['b'])
ans = sum(next(gen_a) == next(gen_b) for _ in range(40000000))
print(ans)
 
gen_a = generator(a, factors['a'], 4)
gen_b = generator(b, factors['b'], 8)
ans = sum(next(gen_a) == next(gen_b) for _ in range(5000000))
print(ans)

2017 Day 14

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
56
from collections import deque
from functools import reduce
from operator import xor
 
def knot_logic(sizes, iterations=64):
    circle = deque(range(256))
    skip = 0
    for _ in range(iterations):
        for group_size in sizes:
            knot = [circle.popleft() for _ in range(group_size)]
            circle += reversed(knot)
            circle.rotate(-skip)
            skip += 1
    unwind = iterations * sum(sizes) + skip * (skip-1) // 2
    circle.rotate(unwind)
    return list(circle)
 
 
def knot_hashing(word):
    ascii_sizes = [ord(c) for c in word] + [17, 31, 73, 47, 23]
    numbers = knot_logic(ascii_sizes)
    SIZE = 256
    BLOCK_SIZE = 16
    block = lambda i: numbers[i*BLOCK_SIZE : (i+1)*BLOCK_SIZE]
    dense = [reduce(xor, block(i)) for i in range(SIZE // BLOCK_SIZE)]
    return ''.join(f'{n:02x}' for n in dense)
 
 
instructions = 'stpzcrnm'
maze = set()
for row in range(128):
    word = f'{instructions}-{row}'
    hex_hash = knot_hashing(word)
    bin_hash = f'{int(hex_hash, 16):0128b}'
    for i, n in enumerate(bin_hash):
        if n == '1':
            maze.add((row, i))
print(len(maze))
 
def dfs(start):
    stack = [start]
    while stack:
        (x, y) = stack.pop()
        for dx, dy in DELTAS:
            candidate = x+dx, y+dy
            if candidate in maze:
                stack.append(candidate)
                maze.remove(candidate)
 
 
DELTAS = ((1, 0), (-1, 0), (0, 1), (0, -1))
regions = 0
while maze:
    dfs(maze.pop())
    regions += 1
print(regions)

2017 Day 13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import itertools
 
data = open('input').read().splitlines()
lines = [line.split(': ') for line in data]
heights = {int(pos): int(height) for pos, height in lines}
 
def scanner(height, time):
    offset = time % ((height - 1) * 2)
 
    return 2 * (height - 1) - offset if offset > height - 1 else offset
 
print(sum(pos * heights[pos] for pos in heights if scanner(heights[pos], pos) == 0))
 
print(next(wait for wait in itertools.count() if not any(scanner(heights[pos], wait + pos) == 0 for pos in heights)))

2017 Day 12

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
from collections import deque
 
data = open('input').readlines()
 
graph = {}
 
for line in data:
    pipe, neighbours = line.split(' <-> ')
    pipe = int(pipe)
    neighbours = [int(n) for n in neighbours.split(', ')]
    graph[pipe] = neighbours
 
 
def bfs(starting_point):
    connections = set()
    que = deque([starting_point])
    while que:
        current = que.popleft()
        connections.add(current)
        for node in graph[current]:
            if node not in connections:
                que.append(node)
    return connections
 
 
seen = bfs(0)
islands = 1
first_island_size = len(seen)
 
for pipe in graph:
    if pipe not in seen:
        pipe_island = bfs(pipe)
        islands += 1
        seen |= pipe_island
 
print(first_island_size)
print(islands)

2017 Day 11

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
data = open('input').read().split(',')
 
to_cube = {
    'n':  (0, 1, -1),
    'nw': (-1, 1, 0),
    'sw': (-1, 0, 1),
    's':  (0, -1, 1),
    'se': (1, -1, 0),
    'ne': (1, 0, -1),
}
position = (0, 0, 0)
 
 
def move(a, b):
    b = to_cube[b]
    return (a[0]+b[0], a[1]+b[1], a[2]+b[2])
 
def distance_to_origin(a):
    return sum(map(abs, a)) // 2
 
 
distances = set()
 
for direction in data:
    position = move(position, direction)
    dist = distance_to_origin(position)
    distances.add(dist)
 
print(distance_to_origin(position))
print(max(distances))

2017 Day 10

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
from collections import deque
from functools import reduce
from operator import xor
 
 
def knot_logic(sizes, iterations=64):
    circle = deque(range(256))
    skip = 0
    for _ in range(iterations):
        for group_size in sizes:
            knot = [circle.popleft() for _ in range(group_size)]
            circle += reversed(knot)
            circle.rotate(-skip)
            skip += 1
    unwind = iterations * sum(sizes) + skip * (skip-1) // 2
    circle.rotate(unwind)
    return list(circle)
 
 
def knot_hashing(word):
    ascii_sizes = [ord(c) for c in word] + [17, 31, 73, 47, 23]
    numbers = knot_logic(ascii_sizes)
    SIZE = 256
    BLOCK_SIZE = 16
    block = lambda i: numbers[i*BLOCK_SIZE : (i+1)*BLOCK_SIZE]
    dense = [reduce(xor, block(i)) for i in range(SIZE // BLOCK_SIZE)]
    return ''.join(f'{n:02x}' for n in dense)
 
 
data = open('input').read().strip()
int_sizes = [int(n) for n in data.split(',')]
 
first_hash = knot_logic(int_sizes, iterations=1)
first = first_hash[0] * first_hash[1]
print(first)
 
second = knot_hashing(data)
print(second)

2017 Day 09

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
data = open('input').read().strip()
garbage_cleaned = 0
nesting_level = 0
score = 0
i = 0
while i < len(data):
    if data[i] == '<':
        i += 1
        while data[i] != '>':
            if data[i] == '!':
                i += 1
            else:
                garbage_cleaned += 1
            i += 1
    elif data[i] == '{':
        nesting_level += 1
    elif data[i] == '}':
        score += nesting_level
        nesting_level -= 1
    i += 1
print(score)
print(garbage_cleaned)

2017 Day 08

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
# dict and defaultdict are almost the same except for the fact that defaultdict never raises a KeyError.
# defaultdict provides a default value for keys that do not exist.
# even when just calling a key in an empty defaultdict, the key will be added and a default value is generated.
# when the int class is passed as the default_factory argument, a defaultdict is created with default value as zero.
 
from collections import defaultdict
from operator import lt, gt, eq, ne, le, ge
 
data = open('input').read().splitlines()
 
registers = defaultdict(int)
operators = {
    '<': lt,
    '>': gt,
    '==': eq,
    '!=': ne,
    '<=': le,
    '>=': ge,
}
maximum = 0
 
for line in data:
    register, instruction, amount, _, cond_reg, operator, value = line.split()
    directon = 1 if instruction == 'inc' else -1
    if operators[operator](registers[cond_reg], int(value)):   # example: if gt(hx,-10):
        registers[register] += directon * int(amount)
        maximum = max(registers[register], maximum)
 
print(max(registers.values()))
print(maximum)

2017 Day 07

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
from collections import Counter
import re
data = open('input').read().splitlines()
weights = {}
children = {}
for line in data:
    node, weight, *kids = re.findall(r"(\w+)", line)
    weights[node] = int(weight)
    children[node] = kids
#print(weights)
#print(children)
 
all_nodes = set(weights)
all_kids = {kid for kids in children.values() for kid in kids}
root = (all_nodes - all_kids).pop()
print(root)
 
def find_offsprings_weights(node):
    kids_weights = [find_offsprings_weights(kid) for kid in children[node]]
    unique_weights = Counter(kids_weights).most_common()
    if len(unique_weights) == 2:
        (correct_total, _), (wrong_total, _) = unique_weights
        difference = correct_total - wrong_total
        wrong_node = children[node][kids_weights.index(wrong_total)]
        wrong_weight = weights[wrong_node]
        print(wrong_weight + difference)
        return weights[node] + sum(kids_weights) + difference
    return weights[node] + sum(kids_weights)
 
find_offsprings_weights(root)

2017 Day 06

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
from collections import OrderedDict
 
data = open('input').read().split()
data = list(map(int, data))
 
size = len(data) # = 16 registers
seen = OrderedDict()
 
# A tuple is a collection which is ordered (not to be confused with sorted) and unchangeable.
# A set is a collection which is sorted (order is not preserved) and contains only unique elements.
 
# Does Python have an ordered set?
# No, but you can use collections.OrderedDict with just keys (and values as None)
 
# Using while ... not in ... we can check if/when a collection pattern reoccurs.
 
while tuple(data) not in seen:
    seen[tuple(data)] = None
    max_value = max(data)
    max_index = data.index(max_value)
    data[max_index] = 0
    to_every, to_some = divmod(max_value, size)
    data = [d + to_every for d in data]
    for i in range(to_some):
        data[(max_index + i+1) % size] += 1
print(len(seen))
# Lookup in the OrderedDict keys, the index of when we first encountered the duplicate value.
loop_start = list(seen.keys()).index(tuple(data))
print(len(seen) - loop_start)

2017 Day 05 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
data = open('input').read().splitlines()
data = list(map(int, data))
ptr = 0
steps = 0
while ptr < len(data):
    offset = data[ptr]
    if offset >= 3:
        data[ptr] -= 1
    else:
        data[ptr] += 1
    ptr += offset
    steps += 1
print(steps)

2017 Day 05 Part 01

1
2
3
4
5
6
7
8
9
10
data = open('input').read().splitlines()
data = list(map(int, data))
ptr = 0
steps = 0
while ptr < len(data):
    offset = data[ptr]
    data[ptr] += 1
    ptr += offset
    steps += 1
print(steps)

2017 Day 04 Part 02

1
2
3
4
5
6
7
8
9
10
data = open('input').read().splitlines()
valid = 0
words = []
for d in data:
    words = d.split()
    words = [''.join(sorted(word)) for word in words]
    uniq = set(words)
    if len(words) == len(uniq):
        valid+=1
print(valid)

2017 Day 04 Part 01

1
2
3
4
5
6
7
8
9
data = open('input').read().splitlines()
valid = 0
words = []
for d in data:
    words = d.split()
    uniq = set(words)
    if len(words) == len(uniq):
        valid+=1
print(valid)

2017 Day 03 Part 02

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
data = open('input').read().split()
number = int(data[0])
 
grid = {(0, 0): 1}
neighbours = lambda x, y: [(x+1, y), (x, y+1), (x+1, y+1), (x-1, y-1),
                           (x-1, y), (x, y-1), (x+1, y-1), (x-1, y+1)]
 
 
def set_value(point):
    grid[point] = sum(grid.get(neighbour, 0) for neighbour in neighbours(*point))
    return grid[point]
 
 
def iterate_through_spiral(ring=0):
    while True:
        ring += 1
        for y in range(-ring + 1, ring): yield set_value((ring,  y))
        for x in range(ring, -ring, -1): yield set_value((x,  ring))
        for y in range(ring, -ring, -1): yield set_value((-ring, y))
        for x in range(-ring, ring + 1): yield set_value((x, -ring))
 
 
for value in iterate_through_spiral():
    if value > number:
        print(value)
        break

2017 Day 03 Part 01

1
2
3
4
5
6
7
8
9
10
data = open('input').read().split()
number = int(data[0])
# double-asterisks (**) in Python is a 
# 'to the power of' exponentiation operator
# example: 2 ** 6 = 64
spiral_corner = int(number ** 0.5)
remaining_steps = number % spiral_corner ** 2
side_length = spiral_corner + 1
towards_middle = remaining_steps % (side_length // 2)
print(side_length - towards_middle)

2017 Day 02 Part 02

1
2
3
4
5
6
7
8
9
10
11
data = open('input').read().splitlines()
delta = 0
nums = []
for line in data:
    nums = line.split('\t')
    nums = list(map(int, nums))
    for a in nums:
        for b in nums:
            if a % b == 0 and a != b:
                delta += a // b
print(delta)

2017 Day 02 Part 01

1
2
3
4
5
6
7
8
data = open('input').read().splitlines()
delta = 0
nums = []
for line in data:
    nums = line.split('\t')
    nums = list(map(int, nums))
    delta += max(nums) - min(nums)
print(delta)

2017 Day 01

1
2
3
4
5
6
7
8
9
data = open('input').read().strip()
data = list(map(int, data))
 
def solve(digits, second_part=False):
    jump = 1 if not second_part else len(digits)//2
    return sum(n for i, n in enumerate(digits) if n == digits[i-jump])
 
print(solve(data))
print(solve(data, 'part 2'))

2017 Day 01 Part 02

1
2
3
4
5
6
7
8
9
10
11
data = open('input').read().strip()
data = list(map(int, data))
count = 0
offset = len(data)//2
for i, n in enumerate(data):
    if n == data[i - offset]:
        count += data[i]
print(count)
# Single slash(/) in python is used to perform classic division
# whereas double slash in python (//) is used to perform floor division.
# Floor division means rounding down to the nearest whole number.

2017 Day 01 Part 01

1
2
3
4
5
6
7
data = open('input').read().strip()
data = list(map(int, data))
count = 0
for i, n in enumerate(data):
    if n == data[i-1]:
        count += data[i]
print(count)