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 Part 02

1
 

2017 Day 19 Part 01

1
 

2017 Day 18 Part 02

1
 

2017 Day 18 Part 01

1
 

2017 Day 17 Part 02

1
 

2017 Day 17 Part 01

1
 

2017 Day 16 Part 02

1
 

2017 Day 16 Part 01

1
 

2017 Day 15 Part 02

1
 

2017 Day 15 Part 01

1
 

2017 Day 14 Part 02

1
 

2017 Day 14 Part 01

1
 

2017 Day 13 Part 02

1
 

2017 Day 13 Part 01

1
 

2017 Day 12 Part 01 + 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
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 Part 01 + 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
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 Part 01 + 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
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 Part 01 + 02

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 Part 01 + 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
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 Part 01 + 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
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 Part 01 + 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
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 Part 01 + 02 v2

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)