AoC 2020 Advent of Code (Python)

By telleropnul, December 31, 2020

Advent of Code AoC) 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: adventofcode2020inputs.zip

2020 Day 25

1
2
3
4
5
6
7
8
9
10
11
12
13
14
input = open('input').read().splitlines()
card_public, door_public = [int(key) for key in input]
value = 1
loop_size = 0
 
while value != card_public:
    value = (value * 7) % 20201227
    loop_size += 1
 
value = 1
for i in range(loop_size):
    value = (value * door_public) % 20201227   
 
print(value)

2020 Day 24 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from collections import defaultdict
from re import sub
 
def normalize(instruction):
    directions = defaultdict(int)
    for direction_pair in [("se", "nw"), ("sw", "ne"), ("e", "w")]:
        directions[direction_pair[0]] = \
        instruction.count(direction_pair[0]) - instruction.count(direction_pair[1])
 
        for direction in direction_pair:
            instruction = sub(direction, "", instruction)
 
    return tuple((directions["sw"] + directions["se"], directions["e"]  + directions["se"]))
 
def pass_day(this_day):
    for tile in list(this_day.keys()):
        if this_day[tile] == 0:
            for neighbor in [( 1, 0), (0,  1), ( 1,  1),
                             (-1, 0), (0, -1), (-1, -1)]:
                this_day[(tile[0] + neighbor[0], tile[1] + neighbor[1])]
 
    next_day = this_day.copy()
 
    for tile in list(this_day.keys()):
        black_neighbors = sum([this_day[(tile[0] + neighbor[0],
                                         tile[1] + neighbor[1])] == 0
                               for neighbor in [( 1, 0), (0,  1), ( 1,  1),
                                                (-1, 0), (0, -1), (-1, -1)]])
 
        if this_day[tile] == 0 and (not black_neighbors or black_neighbors > 2):
            next_day[tile] = 1
        elif this_day[tile] == 1 and black_neighbors == 2:
            next_day[tile] = 0
 
    return next_day
 
 
color_of_tiles = defaultdict(lambda: 1)
 
input = open('input').read().splitlines()
for instruction in input:
    location = normalize(instruction)
    color_of_tiles[location] = (color_of_tiles[location] + 1) % 2
 
for _ in range(100):
    color_of_tiles = pass_day(color_of_tiles)
 
print(sum([color == 0 for color in color_of_tiles.values()]))

2020 Day 24 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from collections import defaultdict
from re import sub
 
def normalize(instruction):
    directions = defaultdict(int)
    for direction_pair in [("se", "nw"), ("sw", "ne"), ("e", "w")]:
        directions[direction_pair[0]] = \
        instruction.count(direction_pair[0]) - instruction.count(direction_pair[1])
 
        for direction in direction_pair:
            instruction = sub(direction, "", instruction)
 
    return tuple((directions["sw"] + directions["se"], directions["e"]  + directions["se"]))
 
 
flips_per_tile = defaultdict(int)
 
input = open('input').read().splitlines()
for instruction in input:
    flips_per_tile[normalize(instruction)] += 1
 
print(sum([flips % 2 for flips in flips_per_tile.values()]))

2020 Day 23 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
27
28
29
30
31
32
def initialize():
    input = open('input').read().splitlines()
    input = [int(cup) for cup in input[0]]
    cups = {**{cup: cup + 1 for cup in range(10, 1000000)},
            **{input[i]: input[i + 1] for i in range(8)}}
    cups[input[8]] = 10
    cups[1000000] = input[0]
 
    return cups, input[0]
 
def move(cups, current_cup):
    cup = current_cup
    taken = [(cup := cups[cup]) for i in range(3)]
 
    destination = current_cup - 1
    while not destination or destination in taken:
        if not destination:
            destination = 1000000
        else:
            destination -= 1
 
    cups[current_cup], cups[destination], cups[taken[-1]] = cups[taken[-1]], cups[current_cup], cups[destination]
 
    return cups, cups[current_cup]
 
 
cups, current_cup = initialize()
 
for _ in range(10000000):
    cups, current_cup = move(cups, current_cup)
 
print(cups[1] * cups[cups[1]])

2020 Day 23 Part 01

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
def initialize():
    input = open('input').read().splitlines()
    input = [int(cup) for cup in input[0]]
    cups = {input[i]: input[i + 1] for i in range(8)}
    cups[input[8]] = input[0]
 
    return cups, input[0]
 
def move(cups, current_cup):
    cup = current_cup
    taken = [(cup := cups[cup]) for i in range(3)]
 
    destination = current_cup - 1
    while not destination or destination in taken:
        if not destination:
            destination = 9
        else:
            destination -= 1
 
    cups[current_cup], cups[destination], cups[taken[-1]] = cups[taken[-1]], cups[current_cup], cups[destination]
 
    return cups, cups[current_cup]
 
 
cups, current_cup = initialize()
 
for _ in range(100):
    cups, current_cup = move(cups, current_cup)
 
cup = 1
print("".join([str((cup := cups[cup])) for _ in range(8)]))

2020 Day 22 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
27
28
29
30
31
32
33
34
35
36
37
38
39
from collections import deque
 
def build_decks(raw_input):
    player, decks = 0, [deque(), deque()]
    for line in raw_input:
        if not len(line):
            player = 1
            continue
        if "Player" not in line:
            decks[player].append(int(line))
 
    return decks
 
def play(decks):
    known = set()
    while len(decks[0]) and len(decks[1]):
        if (state := tuple(decks[0])) in known:
            return 0, None
        known.add(state)
 
        card_0, card_1 = decks[0].popleft(), decks[1].popleft()
 
        if len(decks[0]) < card_0 or len(decks[1]) < card_1:
            if card_0 > card_1:
                decks[0].extend([card_0, card_1])
            else:
                decks[1].extend([card_1, card_0])
        else:
            if not play([deque(list(decks[0])[:card_0]), deque(list(decks[1])[:card_1])])[0]:
                decks[0].extend([card_0, card_1])
            else:
                decks[1].extend([card_1, card_0])
 
    return min(len(decks[1]), 1), decks
 
input = open('input').read().splitlines()
winner, decks = play(build_decks(input))
winning_score = sum([decks[winner].pop() * multiplier for multiplier in range(1, len(decks[winner]) + 1)])
print(winning_score)

2020 Day 22 Part 01

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
from collections import deque
 
def build_decks(raw_input):
    player, decks = 0, [deque(), deque()]
    for line in raw_input:
        if not len(line):
            player = 1
            continue
        if "Player" not in line:
            decks[player].append(int(line))
 
    return decks
 
input = open('input').read().splitlines()
decks = build_decks(input)
 
while len(decks[0]) and len(decks[1]):
    card_0, card_1 = decks[0].popleft(), decks[1].popleft()
    if card_0 > card_1:
        decks[0].extend([card_0, card_1])
    else:
        decks[1].extend([card_1, card_0])
 
winning_deck = sorted(decks, key = lambda deck: len(deck))[1]
winning_score = sum([winning_deck.pop() * multiplier for multiplier in range(1, len(winning_deck) + 1)])
print(winning_score)

2020 Day 21 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from re import match, sub
from collections import defaultdict
 
def analyse_input(raw_input):
    allergens_dict  = defaultdict(list)
    all_ingredients = []
 
    for line in raw_input:
        ingredients, allergens = [words.split() for words in match(r"((?:(?:\w+) )+)\(contains ((?:(?:\w+) *)+)\)", sub(",", "", line)).group(1, 2)]
 
        for allergen in allergens:
            allergens_dict[allergen].append(set(ingredients))
        all_ingredients.extend(ingredients)
 
    return allergens_dict, all_ingredients
 
def identify_allergen(allergens_in_food):
    for allergen, ingredients in allergens_in_food.items():
        candidates = ingredients[0].intersection(*ingredients)
        if len(candidates) == 1:
            return allergen, list(candidates)[0]
 
def eliminate_combo(allergens_in_food, allergen, ingredient):
    del allergens_in_food[allergen]
    for allergen in allergens_in_food.keys():
        for ingredients in allergens_in_food[allergen]:
            ingredients.discard(ingredient)          
 
input = open('input').read().splitlines()
allergens_in_food, all_ingredients = analyse_input(input)
identified_ingredients = []
 
while len(allergens_in_food):
    allergen, ingredient = identify_allergen(allergens_in_food)
    eliminate_combo(allergens_in_food, allergen, ingredient)
    identified_ingredients.append((allergen, ingredient))
 
identified_ingredients.sort(key = lambda combo: combo[0])
 
result = ",".join([ingredient[1] for ingredient in identified_ingredients])
print(result)

2020 Day 21 Part 01

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
from re import match, sub
from collections import defaultdict
 
def analyse_input(raw_input):
    allergens_dict  = defaultdict(list)
    all_ingredients = []
 
    for line in raw_input:
        ingredients, allergens = [words.split() for words in match(r"((?:(?:\w+) )+)\(contains ((?:(?:\w+) *)+)\)", sub(",", "", line)).group(1, 2)]
 
        for allergen in allergens:
            allergens_dict[allergen].append(set(ingredients))
        all_ingredients.extend(ingredients)
 
    return allergens_dict, all_ingredients
 
def identify_allergen(allergens_in_food):
    for allergen, ingredients in allergens_in_food.items():
        candidates = ingredients[0].intersection(*ingredients)
        if len(candidates) == 1:
            return allergen, list(candidates)[0]
 
def eliminate_combo(allergens_in_food, allergen, ingredient):
    del allergens_in_food[allergen]
    for allergen in allergens_in_food.keys():
        for ingredients in allergens_in_food[allergen]:
            ingredients.discard(ingredient)          
 
 
input = open('input').read().splitlines()
allergens_in_food, all_ingredients = analyse_input(input)
identified_ingredients = set()
 
while len(allergens_in_food):
    allergen, ingredient = identify_allergen(allergens_in_food)
    eliminate_combo(allergens_in_food, allergen, ingredient)
    identified_ingredients.add(ingredient)
 
result = sum([ingredient not in identified_ingredients for ingredient in all_ingredients])
print(result)

2020 Day 20 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
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from re import findall
from collections import defaultdict
 
def build_tiles(raw_input):
    tiles_pixels = defaultdict(list)
    for line in raw_input:
        if not len(line):
            continue
        elif (tile := findall(r"(\d+)", line)):
            tile_id = tile[0]
        else:
            tiles_pixels[tile_id].append([pixel == "#" for pixel in line])
 
    borders = defaultdict(list)
 
    for tile, pixels in tiles_pixels.items():
        borders_pixels = ["".join(["1" if pixel else "0" for pixel in pixels[0]]),
                          "".join(["1" if pixel else "0" for pixel in [p[0] for p in pixels]][::-1]),
                          "".join(["1" if pixel else "0" for pixel in pixels[9]][::-1]),
                          "".join(["1" if pixel else "0" for pixel in [p[9] for p in pixels]])]
 
        borders[tile].append([int(borders_pixels[i], 2) for i in range(4)])
        borders[tile].append([int(borders_pixels[i][::-1], 2) for i in range(4)]) 
 
        borders[tile][1][0], borders[tile][1][2] = borders[tile][1][2], borders[tile][1][0]
 
        for rotation in range(3):
            for side in range(2):
                borders[tile].append(borders[tile][side + 2 * rotation][1:] + \
                                    [borders[tile][side + 2 * rotation][0]])
 
    return tiles_pixels, borders
 
def transform(tile, orientation, tiles_pixels, tiles_borders):
    tiles_pixels[tile] = tiles_pixels[tile][1:-1]
    tiles_pixels[tile] = [tiles_pixels[tile][y][1:-1] for y in range(len(tiles_pixels[tile]))]
 
    tiles_borders[tile] = tiles_borders[tile][orientation]
 
    if orientation % 2:
        tiles_pixels[tile] = tiles_pixels[tile][::-1]
 
    for rotation in range(orientation // 2):
        tiles_pixels[tile] = [[tiles_pixels[tile][x][y] for x in range(len(tiles_pixels[tile][y]))][::-1]
                              for y in range(len(tiles_pixels[tile]))]
 
def find_top_left(puzzle, tiles_borders, tiles_neighbors, tiles_pixels):
    for tile, borders in tiles_borders.items():
        for other_tile, other_border in tiles_borders.items():
            if tile == other_tile:
                continue
 
            for border_variation in [borders[0]]:
                for border in border_variation:
                    if border in (b for b_var in other_border for b in b_var):
                        tiles_neighbors[tile].add((other_tile, border, int(f"{border:08b}"[::-1], 2)))
 
    for tile, neighbors in tiles_neighbors.items():
        if len(neighbors) == 2:
            puzzle[(0,0)] = tile
            break
 
    for orientation in range(8):
        if sum([neighbor[1] in [tiles_borders[tile][orientation][side] for side in (2, 3)]
                                for neighbor in tiles_neighbors[tile]]) == 2:
            break
 
    transform(tile, orientation, tiles_pixels, tiles_borders)
 
def find_top_row(puzzle, puzzle_size, tiles_borders, tiles_pixels):
    for position in range(1, puzzle_size):
        next_border = int(f"{tiles_borders[puzzle[(position - 1, 0)]][3]:010b}"[::-1], 2)
        for tile in tiles_pixels.keys():
            if tile in puzzle.values():
                continue
            for orientation in range(8):
                if next_border == tiles_borders[tile][orientation][1]:
                    break
            else:
                continue
            break
 
        transform(tile, orientation, tiles_pixels, tiles_borders)
        puzzle[(position, 0)] = tile
 
def find_remaining_rows(puzzle, puzzle_size, tiles_borders, tiles_pixels):
    for position_y in range(1, puzzle_size):
        for position_x in range(puzzle_size):
            next_border = int(f"{tiles_borders[puzzle[(position_x, position_y - 1)]][2]:010b}"[::-1], 2)
            for tile in tiles_pixels.keys():
                if tile in puzzle.values():
                    continue
                for orientation in range(8):
                    if next_border == tiles_borders[tile][orientation][0]:
                        break
                else:
                    continue
                break
 
            transform(tile, orientation, tiles_pixels, tiles_borders)
            puzzle[(position_x, position_y)] = tile
 
def assemble_puzzle(puzzle, puzzle_size, tiles_pixels):
    full_puzzle = []
 
    for piece_y in range(puzzle_size):
        for row in range(len(tiles_pixels[puzzle[(0, piece_y)]])):
            full_row = []
            for piece_x in range(puzzle_size):
                full_row.extend(["#" if value else " " for value in tiles_pixels[puzzle[(piece_x, piece_y)]][row]])
            full_puzzle.append(full_row)
 
    return full_puzzle
 
def find_dragon(full_puzzle):
    dragon = ["                  # ",
              "#    ##    ##    ###",
              " #  #  #  #  #  #   "]
    dragon_offsets = [(x, y) for x in range(len(dragon[0])) 
                             for y in range(len(dragon))
                             if dragon[y][x] == "#"]
    found = False
 
    for flip in range(2):
        for rotation in range(3):
            for y in range(len(full_puzzle) - len(dragon)):
                for x in range(len(full_puzzle[0]) - len(dragon[0])):
                    for x_dragon, y_dragon in dragon_offsets:
                        if not full_puzzle[y + y_dragon][x + x_dragon] == "#":
                            break
                    else:
                        found = True
                        for x_dragon, y_dragon in dragon_offsets:
                            full_puzzle[y + y_dragon][x + x_dragon] = "O"
 
            if found:
                return sum([line.count("#") for line in full_puzzle])
 
            full_puzzle = [[full_puzzle[x][y] for x in range(len(full_puzzle[y]))][::-1]
                                              for y in range(len(full_puzzle))]
        full_puzzle = full_puzzle[::-1]
 
 
input = open('input').read().splitlines()
tiles_pixels, tiles_borders = build_tiles(input)
tiles_neighbors, puzzle = defaultdict(set), defaultdict(str)
puzzle_size = int(len(tiles_pixels)**0.5)
 
find_top_left(puzzle, tiles_borders, tiles_neighbors, tiles_pixels)
find_top_row(puzzle, puzzle_size, tiles_borders, tiles_pixels)
find_remaining_rows(puzzle, puzzle_size, tiles_borders, tiles_pixels)
full_puzzle = assemble_puzzle(puzzle, puzzle_size, tiles_pixels)
 
print(find_dragon(full_puzzle))

2020 Day 20 Part 01

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
from re import findall
from collections import defaultdict
 
def get_borders(raw_input):
    tiles = defaultdict(list)
    for line in raw_input:
        if not len(line):
            continue
        elif (tile := findall(r"(\d+)", line)):
            tile_id = tile[0]
        else:
            tiles[tile_id].append([pixel == "#" for pixel in line])
 
    borders = defaultdict(list)
 
    for tile, pixels in tiles.items():
        borders_pixels = ["".join(["1" if pixel else "0" for pixel in pixels[0]]),
                          "".join(["1" if pixel else "0" for pixel in [p[0] for p in pixels]][::-1]),
                          "".join(["1" if pixel else "0" for pixel in pixels[9]][::-1]),
                          "".join(["1" if pixel else "0" for pixel in [p[9] for p in pixels]])]
 
        borders[tile] = [int(borders_pixels[i][::direction], 2) for i in range(4) for direction in (-1, 1)]
 
    return borders
 
 
input = open('input').read().splitlines()
tile_borders = get_borders(input)
 
result = 1
 
for tile, borders in tile_borders.items():
    neighbors = set()
    for other_tile, other_borders in tile_borders.items():
        if tile == other_tile:
            continue
 
        for border in borders:
            if border in other_borders:
                neighbors.add(other_tile)
 
    if len(neighbors) == 2:
        result *= int(tile)
 
print(result)

2020 Day 19 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
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
def parseArg(a):
	if a[0] == '"':
		return ('letter', a[1])
	else:
		return ('rule', int(a))
 
def parseRules(text):
	lines = text.split('\n')
	rules = {}
	for l in lines:
		ruleNo, parts = l.split(': ')
 
		ruleNo = int(ruleNo)
		parts = parts.split(' | ')
 
		parts = [[parseArg(p) for p in part.split(' ')] for part in parts]
		rules[ruleNo] = parts
 
	return rules
 
 
def matchRule(text, rules, rule, position):
	matches = []
 
	for attempt in rule:
		positions = [position]
 
		for rt, rv in attempt:
			newPositions = []
			for pos in positions:
				if rt == 'rule':
					for p in matchRule(text, rules, rules[rv], pos):
						newPositions.append(p)
				else:
					if position < len(text) and text[position] == rv:
						newPositions.append(pos + 1)
			positions = newPositions
		for pos in positions:
			matches.append(pos)
	return matches
 
rulesText, samplesText = open('input').read().split('\n\n')
rulesText += "\n8: 42 | 42 8\n11: 42 31 | 42 11 31"
 
rules = parseRules(rulesText)
 
if False:
	for no in rules:
		rule = rules[no]
		print(str(no) + ': ' + str(rule))
 
samples = samplesText.split('\n')
matchCount = 0
for sample in samples:
	res = matchRule(sample, rules, rules[0], 0)
	print(sample + ', ' + str(len(sample)) + ', ' + str(res))
	if len([mc for mc in res if mc == len(sample)]) > 0:
		matchCount += 1
print(matchCount)

2020 Day 19 Part 01

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
from re import findall
 
def substitute_rule(id, rules, known):
    if id in known:
        return known[id]
 
    sequence = "("
 
    for next_id in rules[id]:
        if next_id == "|":
            sequence += "|"
            continue
        next_sequence = substitute_rule(next_id, rules, known)
        known[next_id] = next_sequence
        sequence += next_sequence
 
    return sequence + ")"
 
 
input = open('input').read().splitlines()
rules = input[:(index := input.index(''))]
messages  = input[index + 1:]
rules     = {(r := findall(r"(\d+): (.*)", rule)[0])[0]: r[1].split(" ") for rule in rules}
known     = {}
 
for id, rule in rules.items():
    if rule[0][0] == '"':
        known[id] = rule[0][1]
 
rule_zero = substitute_rule("0", rules, known)
 
result= sum((len((best_match := findall(rule_zero, message))) and message == best_match[0][0] for message in messages))
print(result)

2020 Day 18 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
from re import findall
from functools import reduce
 
def solve_equation(equation):
    while "(" in equation:
        index = 0
        while equation[index] != ")":
            if equation[index] == "(":
                opening = index
            index += 1
 
        equation = equation[:opening] + solve_equation(equation[opening + 1:index]) + equation[index + 1:]
 
    values = [int(value) for value in findall(r"\d+", equation)]
    operators = findall(r"[\+\*]", equation)
 
    while "+" in operators:
        index = operators.index("+")
        values = values[:index] + [values[index] + values[index + 1]] + values[index + 2:]
        operators.remove("+")
 
    return str(reduce(lambda a, b: a * b, [int(value) for value in values]))
 
 
input = open('input').read().splitlines()
print(sum((int(solve_equation(equation)) for equation in input)))

2020 Day 18 Part 01

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
from re import findall
 
def solve_equation(equation):
    while "(" in equation:
        index = 0
        while equation[index] != ")":
            if equation[index] == "(":
                opening = index
            index += 1
 
        equation = equation[:opening] + solve_equation(equation[opening + 1:index]) + equation[index + 1:]
 
    values = [int(value) for value in findall(r"\d+", equation)]
    operators = findall(r"[\+\*]", equation)
 
    for index in range(len(operators)):
        if operators[index] == "+":  
            values[0] += values[index + 1]
        else:
            values[0] *= values[index + 1]
 
    return str(values[0])
 
 
input = open('input').read().splitlines()
print(sum((int(solve_equation(equation)) for equation in input)))

2020 Day 17 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from collections import defaultdict
 
def next_state(x, y, z, w, grid):
    neighbors = [(x + x_dir, y + y_dir, z + z_dir, w + w_dir)
                 for x_dir in (-1, 0, 1)
                 for y_dir in (-1, 0, 1)
                 for z_dir in (-1, 0, 1)
                 for w_dir in (-1, 0, 1)
                 if x_dir or y_dir or z_dir or w_dir]
 
    amount_of_neighors = sum([grid[neighbor] for neighbor in neighbors])
 
    if grid[(x, y, z, w)]:
        if (amount_of_neighors == 2 or amount_of_neighors == 3):
            return True
        return False
 
    if amount_of_neighors == 3:
        return True
    return False
 
def cycle(boundary, grid):
    for parameter in ("x_max", "y_max", "z_max", "w_max"):
        boundary[parameter] += 1
    for parameter in ("x_min", "y_min", "z_min", "w_min"):
        boundary[parameter] -= 1
 
    return defaultdict(bool, {(x, y, z, w): next_state(x, y, z, w, grid)
                              for w in range(boundary["w_min"], boundary["w_max"])
                              for z in range(boundary["z_min"], boundary["z_max"])
                              for y in range(boundary["y_min"], boundary["y_max"])
                              for x in range(boundary["x_min"], boundary["x_max"])})
 
 
input = open('input').read().splitlines()
boundary = {"x_max": len(input[0]), "y_max": len(input), "z_max": 1, "w_max": 1,
            "x_min": 0, "y_min": 0, "z_min": 0, "w_min": 0}
grid = defaultdict(bool, {(x, y, 0, 0): input[y][x] == "#"
                            for y in range(boundary["y_max"])
                            for x in range(boundary["x_max"])})
 
for _ in range(6):
    grid = cycle(boundary, grid)
 
print(sum(grid.values()))

2020 Day 17 Part 01

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
from collections import defaultdict
 
def next_state(x, y, z, grid):
    neighbors = [(x + x_dir, y + y_dir, z + z_dir)
                 for x_dir in (-1, 0, 1)
                 for y_dir in (-1, 0, 1)
                 for z_dir in (-1, 0, 1)
                 if x_dir or y_dir or z_dir]
 
    amount_of_neighors = sum([grid[neighbor] for neighbor in neighbors])
 
    if grid[(x, y, z)]:
        if (amount_of_neighors == 2 or amount_of_neighors == 3):
            return True
        return False
 
    if amount_of_neighors == 3:
        return True
    return False
 
def cycle(boundary, grid):
    for parameter in ("x_max", "y_max", "z_max"):
        boundary[parameter] += 1
    for parameter in ("x_min", "y_min", "z_min"):
        boundary[parameter] -= 1
 
    return defaultdict(bool, {(x, y, z): next_state(x, y, z, grid)
                              for z in range(boundary["z_min"], boundary["z_max"])
                              for y in range(boundary["y_min"], boundary["y_max"])
                              for x in range(boundary["x_min"], boundary["x_max"])})
 
 
input = open('input').read().splitlines()
boundary = {"x_max": len(input[0]), "y_max": len(input), "z_max": 1,
            "x_min": 0, "y_min": 0, "z_min": 0}
grid = defaultdict(bool, {(x, y, 0): input[y][x] == "#"
                            for y in range(boundary["y_max"])
                            for x in range(boundary["x_max"])})
 
for _ in range(6):
    grid = cycle(boundary, grid)
 
print(sum(grid.values()))

2020 Day 16 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
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
from re import findall
from functools import reduce
 
def get_fields_and_tickets():
    mode, fields, field_structure, your_ticket, nearby_tickets = 0, set(), {}, [], []
 
    lines = open('input').read().splitlines()
    for line in lines:
        if not len(line) or "ticket" in line:
            mode += 1
            continue
 
        if mode == 0:
            field = findall(r'(.+): (\d+)-(\d+) or (\d+)-(\d+)', line)[0]
            field_structure[field[0]] = [int(num) for num in field[1:]]
            fields = fields | \
                     set(value for value in range(field_structure[field[0]][0], field_structure[field[0]][1] + 1)) | \
                     set(value for value in range(field_structure[field[0]][2], field_structure[field[0]][3] + 1))
 
        if mode == 2:
            your_ticket = [int(value) for value in line.split(",")]
 
        if mode == 4:
            values = [int(num) for num in line.split(",")]
            if not sum(value not in fields for value in values):
                nearby_tickets.append(values)
 
    return field_structure, nearby_tickets, your_ticket
 
 
field_structure, nearby_tickets, your_ticket = get_fields_and_tickets()
possible_positions = {field_name: set(range(len(field_structure))) for field_name in field_structure.keys()}
values_in_position = [[value[i] for value in nearby_tickets] for i in range(len(nearby_tickets[0]))]
 
for field in possible_positions.keys():
    for position in range(len(field_structure)):
        for value in values_in_position[position]:
            if not ((field_structure[field][0] <= value and field_structure[field][1] >= value) or \
                    (field_structure[field][2] <= value and field_structure[field][3] >= value)):
                possible_positions[field].discard(position)
 
possible_positions = {key: value for key, value in sorted(possible_positions.items(), key=lambda item: item[1])}
 
found = set()
 
for field, positions in possible_positions.items():
    value = positions.symmetric_difference(found)
    found.update(value)
    possible_positions[field] = int(list(value)[0])
 
possible_positions = {key: value for key, value in sorted(possible_positions.items(), key=lambda item: item[1])}
 
result = reduce(lambda x, y: x*y, [your_ticket[possible_positions[field]] for field, position in possible_positions.items() if "departure" in field])
print(result)

2020 Day 16 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from re import findall
 
mode, result, fields = 0, 0, set()
 
lines = open('input').read().splitlines()
for line in lines:
    if not len(line) or "ticket" in line:
        mode += 1
        continue
 
    if mode == 0:
        field = [int(num) for num in findall(r'\d+', line)]
        fields = fields | \
                    set(value for value in range(field[0], field[1] + 1)) | \
                    set(value for value in range(field[2], field[3] + 1))
 
    if mode == 4:
        result += sum(int(value) for value in line.split(",") if int(value) not in fields)
 
print(result)

2020 Day 15 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import defaultdict
 
lines = open('input').read().split(",")
numbers = [int(x) for x in lines]
seen = defaultdict(list, {numbers[i]: [i] for i in range(len(numbers))})    
counter, current = len(numbers) - 1, numbers[-1]
 
while (counter := counter + 1) < 30000000:
    if len(seen[current]) == 1:
        seen[(current := 0)].append(counter)
    else:
        seen[(current := seen[current][-1] - seen[current][-2])].append(counter)
 
print(current)

2020 Day 15 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import defaultdict
 
lines = open('input').read().split(",")
numbers = [int(x) for x in lines]
seen = defaultdict(list, {numbers[i]: [i] for i in range(len(numbers))})    
counter, current = len(numbers) - 1, numbers[-1]
 
while (counter := counter + 1) < 2020:
    if len(seen[current]) == 1:
        seen[(current := 0)].append(counter)
    else:
        seen[(current := seen[current][-1] - seen[current][-2])].append(counter)
 
print(current)

2020 Day 14 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from re import match
 
lines = open('input').read().splitlines()
memory = dict()
 
for line in lines:
    if mask_match := match(r"mask = ([X01]+)", line):
        mask = mask_match.group(1)
    else:
        addresses, value = match(r"mem\[(\d+)\] = (\d+)", line).group(1, 2)
        addresses = list(f'{int(addresses):036b}')
        addresses = [["1" if mask[i] == "1" else addresses[i] for i in range(36)]]
 
        for i in range(36):
            if mask[i] == "X":
                addresses = [address[:i] + [bit] + address[i+1:] for bit in ("0", "1") for address in addresses]
 
        for address in addresses:
            memory[tuple(address)] = int(value)
 
print(sum((value for value in memory.values())))

2020 Day 14 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from re import match
 
lines = open('input').read().splitlines()
memory = dict()
 
for line in lines:
    if mask_match := match(r"mask = ([X01]+)", line):
        mask = mask_match.group(1)
    else:
        address, value = match(r"mem\[(\d+)\] = (\d+)", line).group(1, 2)
        value = list(f'{int(value):036b}')
        value = [value[i] if mask[i] == "X" else mask[i] for i in range(36)]
        memory[address] = int("".join(value), 2)
 
print(sum([value for value in memory.values()]))

2020 Day 13 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
from functools import reduce
from re import findall
 
def find_values(offset, increment, ids, pos, index):
    result, i = [], 0
 
    while len(result) < 2:
        if not (offset + i * increment + pos[index]) % ids[index]:
            result.append(offset + i * increment)
        i += 1
 
    return result[0], result[1] - result[0]
 
 
input = open('input').read().splitlines()
ids = findall(r'\d+', input[1])
all_entries = input[1].split(",")
pos = [all_entries.index(id) for id in ids]
ids = [int(id) for id in ids]
 
offset, increment = 0, 1
for i in range (len(ids)):
    offset, increment = find_values(offset, increment, ids, pos, i)
 
print(offset)

2020 Day 13 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
import re
 
input = open('input').read().splitlines()
start = int(input[0])
ids   = [int(id) for id in re.findall(r'\d+', input[1])]
 
wait_time = 0
 
while 0 not in (diff := [(start + wait_time) % id for id in ids]):
    wait_time += 1
 
print(wait_time * ids[diff.index(0)])

2020 Day 12 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
input = open('input').read().splitlines()
x, y, wp_x, wp_y = 0, 0, 10, -1
 
directions = {"N": (0, -1), "E": (1, 0), "S": (0, 1), "W": (-1, 0)}
rot_r, rot_l = [("R", 90), ("L", 270)], [("L", 90), ("R", 270)]
 
for instruction in input:
    op, value = instruction[0], int("".join(instruction[1:]))
 
    if   op == "F":
        x, y = x + value * wp_x, y + value * wp_y
 
    elif op in ["R", "L"]:
        if value == 180:
            wp_x, wp_y = -wp_x, -wp_y
 
        elif (op, value) in rot_r:
            wp_x, wp_y = -wp_y, wp_x
 
        elif (op, value) in rot_l:
            wp_x, wp_y = wp_y, -wp_x
 
    else:
        wp_x, wp_y = wp_x + directions[op][0] * value, wp_y + directions[op][1] * value
 
print(abs(x) + abs(y))

2020 Day 12 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
input = open('input').read().splitlines()
x, y, dir = 0, 0, 1
 
for instruction in input:
    op, value = instruction[0], int("".join(instruction[1:]))
 
    if   op == "N" or (op == "F" and dir == 0):
        y -= value
    elif op == "E" or (op == "F" and dir == 1):
        x += value
    elif op == "S" or (op == "F" and dir == 2):
        y += value
    elif op == "W" or (op == "F" and dir == 3):
        x -= value
    elif op == "R":
        dir = (dir + value // 90) % 4
    elif op == "L":
        dir = (dir - value // 90) % 4
 
print(abs(x) + abs(y))

2020 Day 11 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
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 defaultdict
 
def get_visible_seats(layout):
    visible_seats = defaultdict(list)
 
    for x, y in (area := layout.keys()):
        for dir in [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]:
            x_ray, y_ray = x, y
            while ((x_ray := x_ray + dir[0]), (y_ray := y_ray + dir[1])) in area:
                if layout[(x_ray, y_ray)] != ".":
                    visible_seats[(x, y)].append((x_ray, y_ray))
                    break
 
    return visible_seats
 
def change_seat(x, y, layout, visible_seats):
    if layout[(x, y)] == ".":
        return "."
 
    elif layout[(x, y)] == "L":
        if not sum([layout[pos] == "#" for pos in visible_seats[(x, y)]]):
            return "#"
        return "L"
 
    elif layout[(x, y)] == "#":
        if sum([layout[pos] == "#" for pos in visible_seats[(x, y)]]) >= 5:
            return "L"
        return "#"
 
def next_round(layout, visible_seats):
    next_layout = defaultdict(str)
    for pos, value in list(layout.items()):
        next_layout[pos] = change_seat(*pos, layout, visible_seats)
    return next_layout
 
 
input = open('input').read().splitlines()
layout = defaultdict(str)
 
for y in range(len(input)):
    for x in range(len(input[0])):
        layout[(x, y)] = input[y][x]
 
visible_seats = get_visible_seats(layout)
 
last_seen = defaultdict(str)
while (layout := next_round(layout, visible_seats)) != last_seen:
    last_seen = layout
 
print(sum([seat == "#" for seat in layout.values()]))

2020 Day 11 Part 01

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
from collections import defaultdict
 
def change_seat(x, y, layout):
    if layout[(x, y)] == ".":
        return "."
 
    elif layout[(x, y)] == "L":
        if not sum([layout[pos] == "#"
                           for x_offset in [-1, 0, 1]
                           for y_offset in [-1, 0, 1]
                           if (pos := (x + x_offset, y + y_offset)) in layout.keys()]):
            return "#"
        return "L"
 
    elif layout[(x, y)] == "#":
        if sum([layout[pos] == "#"
                       for x_offset in [-1, 0, 1]
                       for y_offset in [-1, 0, 1]
                       if (pos := (x + x_offset, y + y_offset)) in layout.keys()]) >= 5:
            return "L"
        return "#"
 
def next_round(layout):
    next_layout = defaultdict(str)
    for pos, value in list(layout.items()):
        next_layout[pos] = change_seat(*pos, layout)
    return next_layout
 
input = open('input').read().splitlines()
layout = defaultdict(str)
 
for y in range(len(input)):
    for x in range(len(input[0])):
        layout[(x, y)] = input[y][x]
 
last_seen = defaultdict(str)
while (layout := next_round(layout)) != last_seen:
    last_seen = layout
 
print(sum([seat == "#" for seat in layout.values()]))

2020 Day 10 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import reduce
 
def analyze_sequences(adapters):
    sequences = [1]
 
    for index in range(len(adapters)-1):
        if adapters[index + 1] - adapters[index] == 1:
            sequences[-1] += 1
        elif adapters[index + 1] - adapters[index] == 3:
            sequences.append(1)
 
    return sequences
 
lines = open('input').read().splitlines()
lines = [int(line) for line in lines]
adapters = sorted(lines)
adapters = [0] + adapters + [adapters[-1] + 3]
 
print(reduce(lambda a, b: a * b, [[1, 2, 4, 7][max(0, s - 2)] for s in analyze_sequences(adapters)]))

2020 Day 10 Part 01

1
2
3
4
5
6
7
8
lines = open('input').read().splitlines()
lines = [int(line) for line in lines]
adapters = sorted(lines)
adapters = [0] + adapters + [adapters[-1] + 3]
diffs=[]
for index in range(len(adapters) - 1):
    diffs.append(adapters[index + 1] - adapters[index])
print (diffs.count(1) * diffs.count(3))

2020 Day 09 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
def is_valid(preamble, val):
    for a in range(25):
        for b in range(a+1, len(preamble)):
            if preamble[a] + preamble[b] == val:
                return True
    return False
 
lines = open('input').read().splitlines()
code = [int(line) for line in lines]
 
for start in range(25, len(code)):
    if not is_valid(code[start-25:start], code[start]):
        value = code[start]
        break
 
for start in range(len(code)):
    running_sum = 0
    for length in range(len(code)-start):
        if (running_sum := running_sum + code[start+length]) > value:
            break
        elif running_sum == value:
            print (min(code[start:start+length]) + max(code[start:start+length]))
            exit()

2020 Day 09 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def is_valid(preamble, val):
    for a in range(25):
        for b in range(a+1, len(preamble)):
            if preamble[a] + preamble[b] == val:
                return True
    return False
 
 
lines = open('input').read().splitlines()
code = [int(line) for line in lines]
 
for index in range(25, len(code)):
    if not is_valid(code[index-25:index], code[index]):
        print(code[index])

2020 Day 08 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
27
28
29
30
31
lines = open('input').read().splitlines()
mod_pos = -1
seen = set()
 
while True:
    code = [line.split() for line in lines]
 
    mod_pos += 1
 
    if code[mod_pos][0] == "jmp":
        code[mod_pos][0] = "nop"
    elif code[mod_pos][0] == "nop":
        code[mod_pos][0] = "jmp"
    else:
        continue
 
    accu, pos = 0, 0
 
    while pos not in seen:
        seen.add(pos)
        if code[pos][0] == "acc":
            accu += int(code[pos][1])
        elif code[pos][0] == "jmp":
            pos += int(code[pos][1]) - 1
        pos += 1
 
        if pos == len(code):
            print(accu)
            exit()
 
    seen.clear()

2020 Day 08 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lines = open('input').read().splitlines()
code = [line.split() for line in lines]
accu, pos = 0, 0
seen = set()
 
while pos not in seen:
    seen.add(pos)
 
    if code[pos][0] == "acc":
        accu += int(code[pos][1])
    elif code[pos][0] == "jmp":
        pos += int(code[pos][1]) - 1
 
    pos += 1
 
print(accu)

2020 Day 07 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
from collections import defaultdict
import re
 
lines = open('input').read().splitlines()
# Example: light salmon bags contain 5 dark brown bags, 2 dotted coral bags, 5 mirrored turquoise bags.
bags = defaultdict(set)
reg_container = re.compile('^([a-z]+ [a-z]+)')
reg_contains  = re.compile('(\d+) ([a-z]+ [a-z]+)')
 
for line in lines:
    container = reg_container.findall(line)[0]
    for bag in reg_contains.findall(line):
        bags[container].add(bag)
 
my_bag = "shiny gold"
# print(bags[my_bag])
# {('4', 'clear magenta'), ('2', 'plaid maroon'), ('3', 'mirrored turquoise'), ('5', 'bright crimson')}
 
def has_num_of_bags(bag, bags):
    numbags = []
    for b in bags[bag]:
        numbags.append ( int(b[0]) * has_num_of_bags(b[1], bags) )
    return sum(numbags) + 1
 
print(has_num_of_bags(my_bag, bags) - 1)

2020 Day 07 Part 01

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
from collections import defaultdict
import re
 
lines = open('input').read().splitlines()
# Example: light salmon bags contain 5 dark brown bags, 2 dotted coral bags, 5 mirrored turquoise bags.
bags = defaultdict(set)
reg_container = re.compile('^([a-z]+ [a-z]+)')
reg_contains  = re.compile('\d+ ([a-z]+ [a-z]+)')
for line in lines:
    container = reg_container.findall(line)
    for bag in reg_contains.findall(line):
        bags[bag].update(container)
'''
bags is used as reverse lookup dictionary:
bags['dark brown'] = {'light salmon'}
bags['dotted coral"] = {'light salmon'}
bags['mirrored turquoise'] = {'light salmon'}
After a few iterations:
bags['dark brown'] = {'light salmon'}
bags['dotted coral"] = {'dotted maroon', 'light salmon'}
bags['mirrored turquoise'] = {'light salmon'}
'dotted coral' bag is inside both 'dotted maroon' bag as well as 'light salmon' bag
'''
 
my_bag = "shiny gold"
 
def has_my_bag(my_bag, bags):
    contains_my_bag = set(bags[my_bag])
    # my bag is in {'pale orange', 'dark lime', 'dim fuchsia', 'faded aqua', 'dotted blue', 'drab fuchsia', 'posh gold', 'light lime'}
 
    # for each bag that has my bag (child) find its parent bags.
    # repeat recursively until all outermost bags that deep down contain my bag are found.
    for b in bags[my_bag]:
        contains_my_bag.update(has_my_bag(b, bags))
    return contains_my_bag
 
outer_bags_containing_my_bag = []
outer_bags_containing_my_bag= has_my_bag(my_bag, bags)
print(len(outer_bags_containing_my_bag))

2020 Day 06 Part 02

1
2
3
4
5
6
7
8
9
10
11
input = open('input').read().strip().split("\n\n")
input = [x.split("\n") for x in input]
count = 0
for group in input:
    yes = ''
    prev = 'abcdefghijklmnopqrstuvwxyz'
    for response_string in group:
        yes = ''.join(set(prev).intersection(response_string))
        prev = yes
    count += len(yes)
print(count)

2020 Day 06 Part 01

1
2
3
4
5
6
7
8
9
input = open('input').read().split("\n\n")
input = [x.split("\n") for x in input]
count = 0
for group in input:
    yes = set()
    for response_string in group:
            yes.update(response_string)
    count += len(yes)
print(count)

2020 Day 06 Part 01

1
2
3
4
5
6
7
8
9
10
input = open('input').read().split("\n\n")
input = [x.split("\n") for x in input]
count = 0
for group in input:
    yes = set()
    for response_string in group:
        for response_char in response_string:
            yes.add(response_char)
    count += len(yes)
print(count)

2020 Day 05 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
27
def calc_seat_id(seat):
    row_low, row_high = 0, 127
    col_low, col_high = 0, 7
 
    for char in seat[:7]:
        if char == "B":
            row_low  += (row_high - row_low + 1) // 2
        else:
            row_high -= (row_high - row_low + 1) // 2
 
    for char in seat[7:]:
        if char == "R":
            col_low  += (col_high - col_low + 1) // 2
        else:
            col_high -= (col_high - col_low + 1) // 2
 
    return row_low * 8 + col_low
 
lines = open('input').read().splitlines()
seats = sorted(lines, key = lambda s: s[7:])
seats = sorted(seats, key = lambda s: s[:7], reverse = True)
 
for i in range(len(seats)):
    current_id = calc_seat_id(seats[i])
    if i and prev_id + 2 == current_id:
        print(prev_id + 1)
    prev_id = current_id

2020 Day 05 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
27
28
29
30
def calc_seat_id(seat):
    row_low, row_high = 0, 127
    col_low, col_high = 0, 7
 
    for char in seat[:7]:
        if char == "B":
            row_low  += (row_high - row_low + 1) // 2
        else:
            row_high -= (row_high - row_low + 1) // 2
 
    for char in seat[7:]:
        if char == "R":
            col_low  += (col_high - col_low + 1) // 2
        else:
            col_high -= (col_high - col_low + 1) // 2
 
    return row_low * 8 + col_low
 
lines = open('input').read().splitlines()
 
ids = []
for line in lines:
    ids.append(calc_seat_id(line))
ids.sort()
 
# find the gap in the ascending list of seat ids
for i in range(len(ids)):
    if ids[i+1] != ids[i]+1:
        print(ids[i]+1)
        break

2020 Day 05 Part 01

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
def calc_seat_id(seat):
    '''
               F
 
     |0 1 2 3| 0 |4 5 6 7|
     |8 9 etc| 1 |_ _ _ _|
   L |_ _ _ _| 2 |_ _ _ _| R
     |_ _ _ _|...|_ _ _ _|
     |_ _ _ _|127|_ _ _ _|
 
               B
    '''
    row_low, row_high = 0, 127
    col_low, col_high = 0, 7
 
    for char in seat[:7]:
        if char == "B":
            row_low  += (row_high - row_low + 1) // 2
        else:
            row_high -= (row_high - row_low + 1) // 2
 
    for char in seat[7:]:
        if char == "R":
            col_low  += (col_high - col_low + 1) // 2
        else:
            col_high -= (col_high - col_low + 1) // 2
 
    return row_low * 8 + col_low
 
lines = open('input').read().splitlines()
print(max(calc_seat_id(line) for line in lines))
 
'''
Sort lines in descending order based on last 3 letters
Next, sort lines in ascending order based on first 7 letters
Calculate seat id ONCE, only for seat[0] string.
'''
seats = sorted(lines, key = lambda s: s[7:], reverse = True) 
seats = sorted(seats, key = lambda s: s[:7])
print(calc_seat_id(seats[0]))

2020 Day 04 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def check(passport):
    passes = 0
    for pair in passport:
        code = pair[0]
        val = pair[1]
        match code:
            case "byr":
                if 1920 <= int(val) <= 2002:
                    passes +=1
            case "iyr":
                if 2010 <= int(val) <= 2020:
                    passes +=1
            case "eyr":
                if 2020 <= int(val) <= 2030:
                    passes +=1
            case "hgt":
                if val[-2:] == 'cm' and (150 <= int(val[:-2]) <= 193) or \
                   val[-2:] == 'in' and ( 59 <= int(val[:-2]) <= 76):
                    passes +=1
            case "hcl":
                if len(val) == 7 and val[0] == "#" and sum([char in "0123456789abcdef" for char in val[1:]]) == 6:
                    passes +=1
            case "ecl":
                if val in ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]:
                    passes +=1
            case "pid":
                if sum([char in "0123456789" for char in val]) == 9:
                    passes +=1
                    pidref = val
    if passes == 7:
        return pidref
 
 
lines = open('input').read().strip().split('\n\n')
lines = [line.replace("\n", " ") for line in lines]
valid = []
for line in lines:
    # passport check
    passport = []
    for pair in line.split(" "):
        pair = pair.split(":")
        passport.append(pair)
    result = check(passport)
    if result:
        valid.append(result)
for v in valid:
    print(v)
print("Total:",len(valid))

2020 Day 04 Part 01

1
2
3
4
5
6
7
8
9
10
11
12
13
import re
lines = open('input').read().split('\n\n')
 
valid = 0
for line in lines:
    fields = set()
    for pair in line.split(" "):
        for a in re.findall('(\w*):', pair):
            fields.add(a)
    fields.discard("cid")
    if fields == {'ecl', 'eyr', 'byr', 'iyr', 'hcl', 'hgt', 'pid'}:
        valid += 1
print(valid)

2020 Day 03 Part 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
lines = open('input').read().splitlines()
 
def calc (slope):
    x = -slope[1]
    numtrees = 0
    for y in range(0, len(lines), slope[0]):
        x = (x + slope[1]) % len(lines[0])
        if lines[y][x] == '#':
            numtrees += 1
    return numtrees
 
 
totals = []
for slope in [(1,1), (1,3), (1,5), (1,7), (2,1)]:
    totals.append(calc(slope))
 
product = 1
for total in totals:
    product = total * product
print(product)

2020 Day 03 Part 01

1
2
3
4
5
6
7
8
9
lines = open('input').read().splitlines()
# coordinates are in x,y notation.  rows = y values, columns = x values.
x = -3
numtrees = 0
for y,line in enumerate(lines):
    x = (x + 3) % len(lines[0]) # 0 3 6 9 etc.
    if lines[y][x] == '#':
        numtrees += 1
print(numtrees)

2020 Day 02 Part 02

1
2
3
4
5
6
7
8
9
import re
 
lines = open('input').read().splitlines()
valid = 0
for line in lines:
    for pos1, pos2, char, pwd in re.findall('(\d*)-(\d*) (\w): (\w*)', line):
        if (pwd[int(pos1)-1] == char) ^ (pwd[int(pos2)-1] == char): # xor
                valid += 1
print(valid)

2020 Day 02 Part 01

1
2
3
4
5
6
7
8
9
import re
 
lines = open('input').read().splitlines()
valid = 0
for line in lines:
    for low, high, char, pwd in re.findall('(\d*)-(\d*) (\w): (\w*)', line):
        if int(low) <= pwd.count(char) <= int(high):
            valid += 1
print(valid)

2020 Day 01 Part 01 + 02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def p1(s):
    for e in s:
        if 2020-e in s:
            return e*(2020-e)
 
def p2(s):
    for i in s:
        for j in s:
            if 2020-i-j in s:
                return i*j*(2020-i-j)
 
lines = open('input').read().strip().split()
s = set(int(e) for e in lines)
print(p1(s))
print(p2(s))