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 02

1
 

2017 Day 12 Part 01

1
 

2017 Day 11 Part 02

1
 

2017 Day 11 Part 01

1
 

2017 Day 10 Part 02

1
 

2017 Day 10 Part 01

1
 

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)