Restyled README and file structure

Signed-off-by: Ettore Dreucci <ettore.dreucci@gmail.com>
This commit is contained in:
2020-12-25 23:50:29 +01:00
parent 0ba35ffae1
commit d8db2fcd5d
102 changed files with 7 additions and 32 deletions

44
2020/solutions/day_01.py Normal file
View File

@@ -0,0 +1,44 @@
"""AOC Day 1"""
import pathlib
import time
def read_input(input_path: str) -> tuple:
"""take input file path and return appropriate data structure"""
with open(input_path, 'r') as input_file:
entries = list()
is_present = [False]*2020
for entry in input_file.readlines():
entries.append(int(entry))
is_present[int(entry)-1] = True
return (entries, is_present)
def part1(entries: list, is_present: list) -> int:
"""part1 solver take a list of int and a list of bool and return an int"""
for x in entries:
complement = 2020 - x
if complement > 0 and is_present[complement-1]:
return x * complement
return None
def part2(entries: list, is_present: list) -> int:
"""part2 solver take a list of int and a list of bool and return an int"""
for x, i in enumerate(entries):
for y in entries[i:]:
complement = 2020 - x - y
if complement > 0 and is_present[complement-1]:
return x * y * complement
return None
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
entries, is_present = read_input(input_path)
start_time = time.time()
print("Part 1: %d" % part1(entries, is_present))
print("Part 2: %d" % part2(entries, is_present))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

49
2020/solutions/day_02.py Normal file
View File

@@ -0,0 +1,49 @@
"""AOC Day 2"""
import pathlib
import time
import re
def read_input(input_path: str) -> list:
"""take input file path and return appropriate data structure"""
with open(input_path, 'r') as input_file:
rules = list()
for entry in input_file.readlines():
splitted = re.split('-| |: ', entry)
rule = [int(splitted[0]), int(splitted[1]), splitted[2], splitted[3]]
rules.append(rule)
return rules
def part1(entries: list) -> int:
"""part1 solver take a list of tuples and return an int"""
correct_passwords = 0
for entry in entries:
occurrences = entry[3].count(entry[2])
if occurrences in range(entry[0], entry[1]+1):
correct_passwords += 1
return correct_passwords
def part2(entries: list) -> int:
"""part2 solver take a list of tuples and return an int"""
correct_passwords = 0
for entry in entries:
pos_1, pos_2, letter, password = entry[:]
if (password[pos_1-1] == letter) != (password[pos_2-1] == letter):
correct_passwords += 1
return correct_passwords
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
entries = read_input(input_path)
start_time = time.time()
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

59
2020/solutions/day_03.py Normal file
View File

@@ -0,0 +1,59 @@
"""AOC Day 3"""
import pathlib
import time
def read_input(input_path: str) -> list:
"""take input file path and return appropriate data structure"""
with open(input_path, 'r') as input_file:
lines = list()
for line in input_file.readlines():
lines.append(line.strip())
return lines
def slope_tree_check(lines: list, dx: int, dy: int) -> int:
"""check how many trees would be encountered with a specific slope"""
x_pos = 0
y_pos = 0
trees_encountered = 0
line_length = len(lines[0])
while y_pos < len(lines):
if lines[y_pos][x_pos] == '#':
trees_encountered += 1
x_pos = (x_pos + dx) % line_length
y_pos += dy
return trees_encountered
def part1(entries: list) -> int:
"""part1 solver take a list of strings and return an int"""
return slope_tree_check(entries, 3, 1)
def part2(entries: list) -> int:
"""part2 solver take a list of tuples and return an int"""
slopes = [
(1, 1),
(3, 1),
(5, 1),
(7, 1),
(1, 2),
]
prod = 1
for slope in slopes:
prod *= slope_tree_check(entries, slope[0], slope[1])
return prod
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
entries = read_input(input_path)
start_time = time.time()
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

72
2020/solutions/day_04.py Normal file
View File

@@ -0,0 +1,72 @@
"""AOC Day 4"""
import pathlib
import time
import re
def read_input(input_path: str) -> list:
"""take input file path and return appropriate data structure"""
with open(input_path, 'r') as input_file:
data = input_file.read()
entries = re.split(r"(?:\r?\n){2,}", data.strip())
passports = list()
for entry in entries:
passport = dict()
entry = entry.replace('\n', ' ')
data = entry.split(' ')
for field in data:
key, value = field.split(':')[:2]
passport[key] = value
passports.append(passport)
return passports
def check_fields(passport: dict) -> bool:
"""check if a passport contains all the required fields"""
required_fields = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]
for required_field in required_fields:
if required_field not in passport.keys():
return False
elif passport[required_field] == None or passport[required_field] == '':
return False
return True
def check_data(passport: dict) -> bool:
"""check if all passport fields contains correct data"""
return (1920 <= int(passport["byr"]) <= 2002 and
2010 <= int(passport["iyr"]) <= 2020 and
2020 <= int(passport["eyr"]) <= 2030 and
((passport["hgt"].endswith("cm") and 150 <= int(passport["hgt"][:-2]) <= 193) or (passport["hgt"].endswith("in") and 59 <= int(passport["hgt"][:-2]) <= 76)) and
re.match(r"^#[0-9a-f]{6}$", passport["hcl"]) and
passport["ecl"] in ("amb", "blu", "brn", "gry", "grn", "hzl", "oth") and
re.match(r"^[0-9]{9}$", passport["pid"]))
def part1(entries: list) -> int:
"""part1 solver take a list of strings and return an int"""
valid_passports = 0
for passport in entries:
if check_fields(passport):
valid_passports += 1
return valid_passports
def part2(entries: list) -> int:
"""part2 solver take a list of tuples and return an int"""
valid_passports = 0
for passport in entries:
if check_fields(passport) and check_data(passport):
valid_passports += 1
return valid_passports
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
entries = read_input(input_path)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

39
2020/solutions/day_05.py Normal file
View File

@@ -0,0 +1,39 @@
"""AOC Day 5"""
import pathlib
import time
def read_input(input_path: str) -> list:
"""take input file path and return appropriate data structure"""
with open(input_path, 'r') as input_file:
entries = input_file.readlines()
seats = list()
for entry in entries:
entry = entry.strip()
seat = int(''.join(['0' if letter in 'FL' else '1' for letter in entry]), 2)
seats.append(seat)
seats.sort()
return seats
def part1(entries: list) -> int:
"""part1 solver take a list of strings and return an int"""
return max(entries)
def part2(entries: list) -> int:
"""part2 solver take a list of strings and return an int"""
missing = [seat for seat in range(entries[0], entries[-1]) if seat not in entries]
return missing[0]
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
entries = read_input(input_path)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

74
2020/solutions/day_06.py Normal file
View File

@@ -0,0 +1,74 @@
"""AOC Day 6"""
import pathlib
import time
import collections
TEST_INPUT = """abc
a
b
c
ab
ac
a
a
a
a
b"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
groups = input_data.strip().split("\n\n")
yes_count = list()
for group in groups:
persons = group.split("\n")
yes_count.append((len(persons), collections.Counter(''.join(persons))))
return yes_count
def part1(entries: list) -> int:
"""part1 solver take a list of sets and return an int"""
return sum(len(group[1].keys()) for group in entries)
def part2(entries: list) -> int:
"""part2 solver take a list of sets and return an int"""
count = 0
for group in entries:
for key in group[1].keys():
if group[1][key] == group[0]:
count += 1
return count
def test_input_day_6():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 11
assert part2(entries) == 6
def test_bench_day_6(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

103
2020/solutions/day_07.py Normal file
View File

@@ -0,0 +1,103 @@
"""AOC Day 6"""
import pathlib
import time
import collections
TEST_INPUT = """light red bags contain 1 bright white bag, 2 muted yellow bags.
dark orange bags contain 3 bright white bags, 4 muted yellow bags.
bright white bags contain 1 shiny gold bag.
muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.
shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.
dark olive bags contain 3 faded blue bags, 4 dotted black bags.
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.
dotted black bags contain no other bags."""
TEST_INPUT_2 = """shiny gold bags contain 2 dark red bags.
dark red bags contain 2 dark orange bags.
dark orange bags contain 2 dark yellow bags.
dark yellow bags contain 2 dark green bags.
dark green bags contain 2 dark blue bags.
dark blue bags contain 2 dark violet bags.
dark violet bags contain no other bags."""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
rules = input_data.split('\n')
graph = dict()
reverse_graph = dict()
for rule in rules:
container, contents = rule.split('contain')
container = ' '.join(container.split()[:2])
content_graph = dict()
for content in contents.split(','):
if content == " no other bags.":
break
parts = content.split()
amount = int(parts[0])
color = ' '.join(parts[1:3])
content_graph[color] = amount
if color in reverse_graph.keys():
reverse_graph[color].append(container)
else:
reverse_graph[color] = [container]
graph[container] = content_graph
return (graph, reverse_graph)
def part1(reverse_graph: dict, color: str) -> int:
"""part1 solver take a dict of lists and return an int"""
queue = collections.deque(reverse_graph[color])
already_counted = set()
while queue:
container = queue.popleft()
if container not in already_counted:
already_counted.add(container)
if container in reverse_graph.keys():
queue += collections.deque(reverse_graph[container])
return len(already_counted)
def part2(graph: dict, color: str) -> int:
"""part2 solver take a dict of dicts and return an int"""
def search_count(graph: dict, color: str) -> int:
if not graph[color]:
return 1
count = 1
for content, amount in graph[color].items():
count += amount * search_count(graph, content)
return count
return search_count(graph, color)-1
def test_input_day_7():
"""pytest testing function"""
graph, reverse_graph = extract(TEST_INPUT)
assert part1(reverse_graph, "shiny gold") == 4
assert part2(graph, "shiny gold") == 32
graph, _ = extract(TEST_INPUT_2)
assert part2(graph, "shiny gold") == 126
def test_bench_day_7(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
graph, reverse_graph = extract(input_data)
print("Part 1: %d" % part1(reverse_graph, "shiny gold"))
print("Part 2: %d" % part2(graph, "shiny gold"))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

106
2020/solutions/day_08.py Normal file
View File

@@ -0,0 +1,106 @@
"""AOC 2020 Day 8"""
import pathlib
import time
TEST_INPUT = """nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6"""
class Instruction:
"""Instruction class hold an operation, an argument and an execution counter"""
op = None
arg = None
def __init__(self, op, arg):
self.op = op
self.arg = arg
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
instructions = list()
for instruction in input_data.split('\n'):
op, arg = instruction.split(' ')
arg = int(arg)
assert op in ('acc', 'jmp', 'nop')
instructions.append(Instruction(op, arg))
return instructions
def run(entries: list) -> (int, bool):
"""run instructions"""
instruction_counter = 0
prev_instruction_counter = 0
accumulator = 0
exec_counter = [0]*len(entries)
while instruction_counter < len(entries):
prev_instruction_counter = instruction_counter
instruction = entries[instruction_counter]
if exec_counter[instruction_counter] != 0:
return accumulator, False
if instruction.op == 'nop':
instruction_counter += 1
elif instruction.op == 'acc':
accumulator += instruction.arg
instruction_counter += 1
elif instruction.op == 'jmp':
instruction_counter += instruction.arg
else:
raise ValueError('Invalid instruction operation %s' % instruction.op)
exec_counter[prev_instruction_counter] += 1
return accumulator, True
def part1(entries: list) -> int:
"""part1 solver"""
return run(entries)[0]
def part2(entries: list) -> int:
"""part2 solver"""
for instruction in entries:
if instruction.op == 'acc':
continue
original_instruction = instruction.op
instruction.op = 'jmp' if instruction.op == 'nop' else 'nop'
accumulator, success = run(entries)
if success:
return accumulator
instruction.op = original_instruction
def test_input_day_8():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 5
assert part2(entries) == 8
def test_bench_day_8(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

87
2020/solutions/day_09.py Normal file
View File

@@ -0,0 +1,87 @@
"""AOC 2020 Day 9"""
import pathlib
import time
import itertools
TEST_INPUT = """35
20
15
25
47
40
62
55
65
95
102
117
150
182
127
219
299
277
309
576"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
entries = list()
for entry in input_data.split('\n'):
entries.append(int(entry))
return entries
def part1(entries: list, preamble_length: int) -> int:
"""part1 solver"""
for index, entry in enumerate(entries[preamble_length:]):
preamble = entries[index:index+preamble_length]
if entry not in [i+j for i, j in itertools.combinations(preamble, 2)]:
return entry
return None
def part2(entries: list, invalid_number: int) -> int:
"""part2 solver"""
left, right = 0, 1
interval_sum = entries[left] + entries[right]
while True:
if interval_sum < invalid_number:
right += 1
interval_sum += entries[right]
elif interval_sum > invalid_number:
interval_sum -= entries[left]
left += 1
else:
numbers = sorted(entries[left:right+1])
return numbers[0] + numbers[-1]
def test_input_day_9():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries, 5) == 127
assert part2(entries, 127) == 62
def test_bench_day_9(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
invalid_number = part1(entries, 25)
print("Part 1: %d" % invalid_number)
print("Part 2: %d" % part2(entries, invalid_number))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

111
2020/solutions/day_10.py Normal file
View File

@@ -0,0 +1,111 @@
"""AOC 2020 Day 10"""
import pathlib
import time
TEST_INPUT = """16
10
15
5
1
11
7
19
6
12
4"""
TEST_INPUT_2 = """28
33
18
42
31
14
46
20
48
47
24
23
49
45
19
38
39
11
1
32
25
35
8
17
7
9
4
2
34
10
3"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
entries = [0]
for entry in input_data.split('\n'):
entries.append(int(entry))
entries = sorted(entries)
entries.append(entries[-1]+3)
return entries
def part1(entries: list) -> int:
"""part1 solver"""
jolt_diff_1, jolt_diff_3 = 0, 0
for index in range(len(entries)-1):
diff = entries[index+1] - entries[index]
if diff == 1:
jolt_diff_1 += 1
elif diff == 3:
jolt_diff_3 += 1
return jolt_diff_1*jolt_diff_3
def part2(entries: list) -> int:
"""part2 solver"""
distinct_paths = [0]*(entries[-1]+1)
distinct_paths[0] = 1
for adapter in entries:
distinct_paths[adapter] += distinct_paths[adapter-1] + distinct_paths[adapter-2] + distinct_paths[adapter-3]
return distinct_paths[len(distinct_paths)-1]
def test_input_day_10():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 35
assert part2(entries) == 8
entries = extract(TEST_INPUT_2)
assert part1(entries) == 220
assert part2(entries) == 19208
def test_bench_day_10(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

143
2020/solutions/day_11.py Normal file
View File

@@ -0,0 +1,143 @@
"""AOC 2020 Day 11"""
import pathlib
import time
import copy
TEST_INPUT = """L.LL.LL.LL
LLLLLLL.LL
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL"""
FLOOR = 0
EMPTY_SEAT = 1
OCCUPIED_SEAT = 2
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
entries = []
for row in input_data.split('\n'):
entries.append([])
for seat in row:
if seat == '.':
entries[-1].append(FLOOR)
elif seat == 'L':
entries[-1].append(EMPTY_SEAT)
elif seat == '#':
entries[-1].append(OCCUPIED_SEAT)
else:
raise ValueError("Invalid seat %s" % seat)
return entries
def occupied_adjacent_neighbors(seats: list, row: int, column: int) -> int:
"""return number of occupied adjacent neighbors of a given seat"""
neigh_seats = [(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)]
neighbors = 0
rows = len(seats)
columns = len(seats[0])
for dy, dx in neigh_seats:
nrow, ncolumn = row+dy, column+dx
if 0 <= nrow < rows and 0 <= ncolumn < columns and seats[nrow][ncolumn] == OCCUPIED_SEAT:
neighbors += 1
return neighbors
def occupied_insight_neighbors(seats: list, row: int, column: int) -> int:
"""return number of occupied in-sight neighbors of a given seat"""
neigh_seats = [(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)]
neighbors = 0
rows = len(seats)
columns = len(seats[0])
for dy, dx in neigh_seats:
nrow, ncolumn = row+dy, column+dx
while 0 <= nrow < rows and 0 <= ncolumn < columns:
seat = seats[nrow][ncolumn]
if seat == OCCUPIED_SEAT:
neighbors += 1
break
elif seat == EMPTY_SEAT:
break
nrow += dy
ncolumn += dx
return neighbors
def part1(entries: list) -> int:
"""part1 solver"""
seats = copy.deepcopy(entries)
while True:
new_grid = []
changed = False
for y, row in enumerate(seats):
new_grid.append([])
for x, seat in enumerate(row):
neighbors = occupied_adjacent_neighbors(seats, y, x)
if seat == EMPTY_SEAT and neighbors == 0:
new_grid[-1].append(OCCUPIED_SEAT)
changed = True
elif seat == OCCUPIED_SEAT and neighbors >= 4:
new_grid[-1].append(EMPTY_SEAT)
changed = True
else:
new_grid[-1].append(seat)
if changed:
seats = new_grid
else:
return sum(row.count(OCCUPIED_SEAT) for row in seats)
def part2(entries: list) -> int:
"""part2 solver"""
seats = copy.deepcopy(entries)
while True:
new_grid = []
changed = False
for y, row in enumerate(seats):
new_grid.append([])
for x, seat in enumerate(row):
neighbors = occupied_insight_neighbors(seats, y, x)
if seat == EMPTY_SEAT and neighbors == 0:
new_grid[-1].append(OCCUPIED_SEAT)
changed = True
elif seat == OCCUPIED_SEAT and neighbors >= 5:
new_grid[-1].append(EMPTY_SEAT)
changed = True
else:
new_grid[-1].append(seat)
if changed:
seats = new_grid
else:
return sum(row.count(OCCUPIED_SEAT) for row in seats)
def test_input_day_11():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 37
assert part2(entries) == 26
def test_bench_day_11(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

116
2020/solutions/day_12.py Normal file
View File

@@ -0,0 +1,116 @@
"""AOC 2020 Day 12"""
import pathlib
import time
TEST_INPUT = """F10
N3
F7
R90
F11"""
LEFT, RIGHT = 'L', 'R'
FORWARD = 'F'
NORTH, SOUTH, EAST, WEST = 'N', 'S', 'E', 'W'
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
entries = []
for line in input_data.split('\n'):
instruction = (line[0], int(line[1:]))
entries.append(instruction)
return entries
def rotate_ship(direction: str, rotation: str) -> str:
"""rotate the ship"""
if direction == NORTH:
return WEST if rotation == LEFT else EAST
if direction == SOUTH:
return EAST if rotation == LEFT else WEST
if direction == EAST:
return NORTH if rotation == LEFT else SOUTH
if direction == WEST:
return SOUTH if rotation == LEFT else NORTH
return None
def move(direction: str, x: int, y: int, value: int) -> tuple:
"""move the ship or the waypoint"""
if direction == NORTH:
return (x, y+value)
if direction == SOUTH:
return (x, y-value)
if direction == EAST:
return (x+value, y)
if direction == WEST:
return (x-value, y)
return None
def rotate_waypoint(direction: str, x: int, y: int, value: int) -> tuple:
"""rotate waypoint around ship"""
if (direction == LEFT and value == 90) or (direction == RIGHT and value == 270):
return (-y, x)
if ((direction == LEFT and value == 180) or (direction == RIGHT and value == 180)):
return (-x, -y)
if ((direction == LEFT and value == 270) or (direction == RIGHT and value == 90)):
return (y, -x)
return None
def part1(entries: list) -> int:
"""part1 solver"""
direction = EAST
x_ship, y_ship = 0, 0
for action, value in entries:
if action == FORWARD:
x_ship, y_ship = move(direction, x_ship, y_ship, value)
elif action in (LEFT, RIGHT):
for _ in range(value//90):
direction = rotate_ship(direction, action)
else:
x_ship, y_ship = move(action, x_ship, y_ship, value)
return abs(x_ship) + abs(y_ship)
def part2(entries: list) -> int:
"""part2 solver"""
x_ship, y_ship = 0, 0
x_wp, y_wp = 10, 1
for action, value in entries:
if action == FORWARD:
x_ship += x_wp * value
y_ship += y_wp * value
elif action in (LEFT, RIGHT):
x_wp, y_wp = rotate_waypoint(action, x_wp, y_wp, value)
else:
x_wp, y_wp = move(action, x_wp, y_wp, value)
return abs(x_ship) + abs(y_ship)
def test_input_day_12():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 25
assert part2(entries) == 286
def test_bench_day_12(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

87
2020/solutions/day_13.py Normal file
View File

@@ -0,0 +1,87 @@
"""AOC 2020 Day 13"""
import pathlib
import time
TEST_INPUT = """939
7,13,x,x,59,x,31,19"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
entries = input_data.split('\n')
departure_ts = int(entries[0])
timetable = entries[1].split(',')
ids = [(-index, int(bus_id)) for index, bus_id in enumerate(timetable) if bus_id != 'x']
return (departure_ts, ids)
def egcd(a, b):
""""extended euclidean algorithm"""
if a == 0:
return (b, 0, 1)
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
def modinv(x, m):
"""calculate the modular multiplicative inverse"""
g, inv, _ = egcd(x, m)
assert g == 1
return inv % m
def part1(entries: tuple) -> int:
"""part1 solver"""
wait_time = float('inf')
best_bus_id = -1
min_dep_time = entries[0]
for _, bus_id in entries[1]:
if min_dep_time % bus_id == 0:
multiple = min_dep_time // bus_id
else:
multiple = (min_dep_time // bus_id) + 1
time_diff = (bus_id * multiple) - min_dep_time
if time_diff < wait_time:
wait_time = time_diff
best_bus_id = bus_id
return wait_time * best_bus_id
def part2(entries: tuple) -> int:
"""part2 solver"""
min_timestamp = 0
moduli_product = 1
for _, modulo in entries[1]:
moduli_product *= modulo
for remainder, modulo in entries[1]:
ni = moduli_product // modulo
modulo_inv = modinv(ni, modulo)
min_timestamp += (remainder * ni * modulo_inv)
return min_timestamp % moduli_product
def test_input_day_13():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 295
assert part2(entries) == 1068781
def test_bench_day_13(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

109
2020/solutions/day_14.py Normal file
View File

@@ -0,0 +1,109 @@
"""AOC 2020 Day 14"""
import pathlib
import time
import re
import itertools
TEST_INPUT = """mask = XXXXXXXXXXXXXXXXXXXXXXXXXXXXX1XXXX0X
mem[8] = 11
mem[7] = 101
mem[8] = 0"""
TEST_INPUT_2 = """mask = 000000000000000000000000000000X1001X
mem[42] = 100
mask = 00000000000000000000000000000000X0XX
mem[26] = 1"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
entries = list()
mem_rexp = re.compile(r'mem\[(\d+)\] = (\d+)')
for line in input_data.split('\n'):
if line.startswith('mask'):
entry = {
'type': 'mask',
'value': line[7:].rstrip(),
}
else:
entry = {
'type': 'mem',
'value': mem_rexp.match(line).groups(),
}
entries.append(entry)
return entries
def generate_addresses(addr: str, mask: str) -> list:
"""generate all possible addresses from floating mask"""
generator_bits = list()
addresses = list()
addr = format(int(addr), '036b')
for addr_bit, mask_bit in zip(addr, mask):
if mask_bit == '0':
generator_bits.append(addr_bit)
elif mask_bit == '1':
generator_bits.append('1')
else:
generator_bits.append('01')
for address in itertools.product(*generator_bits):
addresses.append(int(''.join(address), 2))
return addresses
def part1(entries: dict) -> int:
"""part1 solver"""
mem = dict()
mask_set0s = 0
mask_set1s = 0
for entry in entries:
if entry['type'] == 'mask':
mask = entry['value']
mask_set0s = int(mask.replace('X', '1'), 2)
mask_set1s = int(mask.replace('X', '0'), 2)
else:
addr, value = entry['value']
mem[addr] = (int(value) & mask_set0s) | mask_set1s
return sum(mem.values())
def part2(entries: tuple) -> int:
"""part2 solver"""
mem = dict()
mask = ''
for entry in entries:
if entry['type'] == 'mask':
mask = entry['value']
else:
addr, value = entry['value']
for address in generate_addresses(addr, mask):
mem[address] = int(value)
return sum(mem.values())
def test_input_day_14():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 165
entries = extract(TEST_INPUT_2)
assert part2(entries) == 208
def test_bench_day_14(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

68
2020/solutions/day_15.py Normal file
View File

@@ -0,0 +1,68 @@
"""AOC 2020 Day 15"""
import pathlib
import time
TEST_INPUT = """3,1,2"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
entries = list(map(int, input_data.split(',')))
return entries
def calculate_last_spoken(numbers: list, turns: int) -> int:
"""calculate the last spoken number at specified turn"""
spoken = [0]*turns
last_spoken = -1
for turn, number in enumerate(numbers, 1):
spoken[number] = turn
last_spoken = number
for prev_turn in range(len(numbers), turns):
if spoken[last_spoken] != 0:
current_spoken = prev_turn - spoken[last_spoken]
else:
current_spoken = 0
spoken[last_spoken] = prev_turn
last_spoken = current_spoken
return last_spoken
def part1(entries: dict) -> int:
"""part1 solver"""
return calculate_last_spoken(entries, 2020)
def part2(entries: tuple) -> int:
"""part2 solver"""
return calculate_last_spoken(entries, 30000000)
def test_input_day_15():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 1836
assert part2(entries) == 362
def test_bench_day_15(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

136
2020/solutions/day_16.py Normal file
View File

@@ -0,0 +1,136 @@
"""AOC 2020 Day 16"""
import pathlib
import time
import re
TEST_INPUT = """class: 1-3 or 5-7
row: 6-11 or 33-44
seat: 13-40 or 45-50
your ticket:
7,1,14
nearby tickets:
7,3,47
40,4,50
55,2,20
38,6,12"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
values_rexp = re.compile(r'([0-9]+)-([0-9]+) or ([0-9]+)-([0-9]+)')
rules_str, ticket_str, nearby_tickets_str = input_data.split('\n\n')
rules = dict()
nearby_tickets = list()
for rule in rules_str.split('\n'):
field, vals = rule.split(': ')
ranges = values_rexp.match(vals)
rules[field] = (
(int(ranges.group(1)), int(ranges.group(2))),
(int(ranges.group(3)), int(ranges.group(4)))
)
my_ticket = list(map(int, ticket_str.split('\n')[1].split(',')))
for ticket in nearby_tickets_str.split('\n')[1:]:
nearby_tickets.append(list(map(int, ticket.split(','))))
return (rules, my_ticket, nearby_tickets)
def check_field(rule: tuple, field: int) -> bool:
"""check if a field is valid given a rule"""
for min_range, max_range in rule:
if min_range <= field <= max_range:
return True
return False
def find_invalid_field(rules: dict, ticket: list) -> int:
"""check if a ticket is valid or not"""
for field in ticket:
valid = False
for rule in rules.values():
valid = check_field(rule, field)
if valid:
break
if not valid:
return field
return None
def part1(entries: tuple) -> tuple:
"""part1 solver"""
rules, _, nearby_tickets = entries
valid_tickets = list()
invalid_fields_sum = 0
for ticket in nearby_tickets:
invalid_field = find_invalid_field(rules, ticket)
if invalid_field is not None:
invalid_fields_sum += invalid_field
else:
valid_tickets.append(ticket)
return valid_tickets, invalid_fields_sum
def part2(rules: dict, my_ticket: list, valid_tickets: list) -> int:
"""part2 solver"""
acceptable_fields = {key: [] for key in rules.keys()}
field_map = {key: None for key in rules.keys()}
departure_values_product = 1
for name, rule in rules.items():
for index in range(len(my_ticket)):
valid = True
for ticket in valid_tickets:
if not check_field(rule, ticket[index]):
valid = False
break
if valid:
acceptable_fields[name].append(index)
while not all(field is not None for field in field_map.values()):
removed_index = -1
for name, indexes in acceptable_fields.items():
if len(indexes) == 1:
field_map[name] = indexes[0]
removed_index = indexes[0]
break
for name, indexes in acceptable_fields.items():
if removed_index in indexes:
acceptable_fields[name].remove(removed_index)
for name, index in field_map.items():
if name.startswith('departure'):
departure_values_product *= my_ticket[index]
return departure_values_product
def test_input_day_16():
"""pytest testing function"""
entries = extract(TEST_INPUT)
_, invalid_fields_sum = part1(entries)
assert invalid_fields_sum == 71
def test_bench_day_16(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
valid_tickets, invalid_fields_sum = part1(entries)
print("Part 1: %d" % invalid_fields_sum)
print("Part 2: %d" % part2(entries[0], entries[1], valid_tickets))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

95
2020/solutions/day_17.py Normal file
View File

@@ -0,0 +1,95 @@
"""AOC 2020 Day 17"""
import pathlib
import time
import itertools
TEST_INPUT = """.#.
..#
###"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str, dims: int) -> set:
"""take input data and return the appropriate data structure"""
alive_cells = set()
zeros = [0]*(dims-2)
for x_cell, row in enumerate(input_data.split('\n')):
for y_cell, cell in enumerate(row):
if cell == '#':
alive_cells.add((x_cell, y_cell, *zeros))
return alive_cells
def count_alive_neighbors(alive_cells: set, coords: tuple) -> int:
"""return the number of alive neighbors of a given cell"""
alive = 0
ranges = ((c-1, c, c+1) for c in coords)
for cell in itertools.product(*ranges):
if cell in alive_cells:
alive += 1
if coords in alive_cells:
alive -= 1
return alive
def get_cube_limits(alive_cells: set, dims: int) -> list:
"""return cube bounds incremented for expansion"""
limits = list()
for i in range(dims):
low = float('Inf')
high = -float('Inf')
for row in alive_cells:
if row[i] < low:
low = row[i]
elif row[i] > high:
high = row[i]
limits.append(range(low-1, high+2))
return limits
def step_cube(alive_cells: set, dims: int) -> set:
"""return next step alive cells"""
next_step = set()
for cell in itertools.product(*get_cube_limits(alive_cells, dims)):
alive_neighbors = count_alive_neighbors(alive_cells, cell)
if (cell in alive_cells and alive_neighbors in (2, 3)) or alive_neighbors == 3:
next_step.add(cell)
return next_step
def part1(input_data: str) -> int:
"""part1 solver"""
cube = extract(input_data, 3)
for _ in range(6):
cube = step_cube(cube, 3)
return len(cube)
def part2(input_data: str) -> int:
"""part2 solver"""
cube = extract(input_data, 4)
for _ in range(6):
cube = step_cube(cube, 4)
return len(cube)
def test_input_day_17():
"""pytest testing function"""
assert part1(TEST_INPUT) == 112
assert part2(TEST_INPUT) == 848
def test_bench_day_17(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
print("Part 1: %d" % part1(input_data))
print("Part 2: %d" % part2(input_data))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

125
2020/solutions/day_18.py Normal file
View File

@@ -0,0 +1,125 @@
"""AOC 2020 Day 18"""
import pathlib
import time
TEST_INPUT = """1 + (2 * 3) + (4 * (5 + 6))
2 * 3 + (4 * 5)
5 + (8 * 3 + 9 + 3 * 4 * 3)
5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))
((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
return [exp.rstrip() for exp in input_data.split('\n')]
def regular_op(stack: list, num: int) -> list:
"""return the stack after performing a regular math operation"""
while stack:
if stack[-1] == '(':
break
operator, left_operand = stack[-1], stack[-2]
stack = stack[:-2]
if operator == '+':
num = left_operand + num
elif operator == '*':
num = left_operand * num
stack.append(num)
return stack
def advanced_op(stack: list, num: int, sub_expr: bool) -> list:
"""return the stack after performing an advanced math operation"""
if sub_expr:
index = -1
for stack_index, value in enumerate(stack):
if value == '(':
index = stack_index
if index != -1:
target = stack[index+1:]
stack = stack[:index]
else:
target = stack
stack = []
for value in target:
if value != '*':
num *= int(value)
while stack:
if stack[-1] in '(*':
break
operator, left_operand = stack[-1], stack[-2]
stack = stack[:-2]
if operator == '+':
num = left_operand + num
stack.append(num)
return stack
def evaluate(expression: str, advance: bool = False) -> tuple:
"""return the result of an expression evaluated with regular or advanced math operator precendece"""
stack = list()
index = 0
while index < len(expression):
data = expression[index]
if data.isdigit():
num = int(data)
if advance:
stack = advanced_op(stack, num, False)
else:
stack = regular_op(stack, num)
elif data in '*+(':
stack.append(data)
elif data == ')':
num = int(stack[-1])
if advance:
stack = stack[:-1]
stack = advanced_op(stack, num, True)
else:
stack = stack[:-2]
stack = regular_op(stack, num)
index += 1
if advance and len(stack) > 1:
stack = advanced_op(stack[:-1], int(stack[-1]), True)
return stack[0]
def part1(entries: dict) -> int:
"""part1 solver"""
result_sum = 0
for expression in entries:
result_sum += evaluate(expression)
return result_sum
def part2(entries: tuple) -> int:
"""part2 solver"""
result_sum = 0
for expression in entries:
result_sum += evaluate(expression, True)
return result_sum
def test_input_day_18():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 26386
assert part2(entries) == 693942
def test_bench_day_18(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

153
2020/solutions/day_19.py Normal file
View File

@@ -0,0 +1,153 @@
"""AOC 2020 Day 19"""
import pathlib
import time
import re
TEST_INPUT = """0: 4 1 5
1: 2 3 | 3 2
2: 4 4 | 5 5
3: 4 5 | 5 4
4: "a"
5: "b"
ababbb
bababa
abbbab
aaabbb
aaaabbb"""
TEST_INPUT_2 = """42: 9 14 | 10 1
9: 14 27 | 1 26
10: 23 14 | 28 1
1: "a"
11: 42 31
5: 1 14 | 15 1
19: 14 1 | 14 14
12: 24 14 | 19 1
16: 15 1 | 14 14
31: 14 17 | 1 13
6: 14 14 | 1 14
2: 1 24 | 14 4
0: 8 11
13: 14 3 | 1 12
15: 1 | 14
17: 14 2 | 1 7
23: 25 1 | 22 14
28: 16 1
4: 1 1
20: 14 14 | 1 15
3: 5 14 | 16 1
27: 1 6 | 14 18
14: "b"
21: 14 1 | 1 14
25: 1 1 | 1 14
22: 14 14
8: 42
26: 14 22 | 1 20
18: 15 15
7: 14 5 | 1 21
24: 14 1
abbbbbabbbaaaababbaabbbbabababbbabbbbbbabaaaa
bbabbbbaabaabba
babbbbaabbbbbabbbbbbaabaaabaaa
aaabbbbbbaaaabaababaabababbabaaabbababababaaa
bbbbbbbaaaabbbbaaabbabaaa
bbbababbbbaaaaaaaabbababaaababaabab
ababaaaaaabaaab
ababaaaaabbbaba
baabbaaaabbaaaababbaababb
abbbbabbbbaaaababbbbbbaaaababb
aaaaabbaabaaaaababaa
aaaabbaaaabbaaa
aaaabbaabbaaaaaaabbbabbbaaabbaabaaa
babaaabbbaaabaababbaabababaaab
aabbbbbaabbbaaaaaabbbbbababaaaaabbaaabba"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
rules = dict()
messages = list()
rules_input, messages_input = input_data.split('\n\n')[0:2]
for rule_input in rules_input.split('\n'):
rule_id, rule = rule_input.split(': ')
rules[rule_id] = rule
messages = messages_input.split('\n')
return rules, messages
def get_regxp(rule_num: str, rules: dict):
rule = rules[rule_num]
if re.fullmatch('"."', rule):
return rule[1]
rule_parts = rule.split(' | ')
or_rules = []
for part in rule_parts:
numbers = part.split(' ')
or_rules.append(''.join(get_regxp(n, rules) for n in numbers))
return f"(?:{'|'.join(or_rules)})"
def get_regxp_upd(rule_num: str, rules: dict):
if rule_num == '8':
return f"{get_regxp_upd('42', rules)}+"
elif rule_num == '11':
rule_11 = (f"{get_regxp_upd('42', rules)}{{{n}}}{get_regxp_upd('31', rules)}{{{n}}}" for n in range(1, 22))
return f"(?:{'|'.join(rule_11)})"
rule = rules[rule_num]
if re.fullmatch('"."', rule):
return rule[1]
rule_parts = rule.split(' | ')
or_rules = []
for part in rule_parts:
numbers = part.split(' ')
or_rules.append(''.join(get_regxp_upd(n, rules) for n in numbers))
return f"(?:{'|'.join(or_rules)})"
def part1(entries: tuple) -> int:
"""part1 solver"""
rules, messages = entries
regxp_0 = re.compile(get_regxp('0', rules))
return sum(regxp_0.fullmatch(x) is not None for x in messages)
def part2(entries: tuple) -> int:
"""part2 solver"""
rules, messages = entries
regxp_0 = re.compile(get_regxp_upd('0', rules))
return sum(regxp_0.fullmatch(x) is not None for x in messages)
def test_input_day_19():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 2
entries = extract(TEST_INPUT_2)
assert part2(entries) == 12
def test_bench_day_19(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

306
2020/solutions/day_20.py Normal file
View File

@@ -0,0 +1,306 @@
"""AOC 2020 Day 20"""
import pathlib
import time
import collections
import operator
import itertools
TEST_INPUT = """Tile 2311:
..##.#..#.
##..#.....
#...##..#.
####.#...#
##.##.###.
##...#.###
.#.#.#..##
..#....#..
###...#.#.
..###..###
Tile 1951:
#.##...##.
#.####...#
.....#..##
#...######
.##.#....#
.###.#####
###.##.##.
.###....#.
..#.#..#.#
#...##.#..
Tile 1171:
####...##.
#..##.#..#
##.#..#.#.
.###.####.
..###.####
.##....##.
.#...####.
#.##.####.
####..#...
.....##...
Tile 1427:
###.##.#..
.#..#.##..
.#.##.#..#
#.#.#.##.#
....#...##
...##..##.
...#.#####
.#.####.#.
..#..###.#
..##.#..#.
Tile 1489:
##.#.#....
..##...#..
.##..##...
..#...#...
#####...#.
#..#.#.#.#
...#.#.#..
##.#...##.
..##.##.##
###.##.#..
Tile 2473:
#....####.
#..#.##...
#.##..#...
######.#.#
.#...#.#.#
.#########
.###.#..#.
########.#
##...##.#.
..###.#.#.
Tile 2971:
..#.#....#
#...###...
#.#.###...
##.##..#..
.#####..##
.#..####.#
#..#.#..#.
..####.###
..#.#.###.
...#.#.#.#
Tile 2729:
...#.#.#.#
####.#....
..#.#.....
....#..#.#
.##..##.#.
.#.####...
####.#.#..
##.####...
##..#.##..
#.##...##.
Tile 3079:
#.#.#####.
.#..######
..#.......
######....
####.#..#.
.#...#.##.
#.#####.##
..#.###...
..#.......
..#.###..."""
MONSTER_PATTERN = (
' # ',
'# ## ## ###',
' # # # # # # '
)
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> dict:
"""take input data and return the appropriate data structure"""
tiles = dict()
for tile in input_data.split('\n\n'):
parts = tile.split('\n')
tile_id = int(parts[0][5:-1])
tiles[tile_id] = parts[1:]
return tiles
def edge(matrix: list, side: str) -> str:
"""return a side of a matrix"""
if side == 'n':
return matrix[0]
if side == 's':
return matrix[-1]
if side == 'e':
return ''.join(map(operator.itemgetter(-1), matrix))
return ''.join(map(operator.itemgetter(0), matrix))
def get_tiles_corners(tiles: dict) -> dict:
"""return the corners of a set of tiles"""
matching_sides = collections.defaultdict(str)
corners = {}
for id_tile1, id_tile2 in itertools.combinations(tiles, 2):
tile1, tile2 = tiles[id_tile1], tiles[id_tile2]
for side_a in 'nsew':
for side_b in 'nsew':
edge_a, edge_b = edge(tile1, side_a), edge(tile2, side_b)
if edge_a == edge_b or edge_a == edge_b[::-1]:
matching_sides[id_tile1] += side_a
matching_sides[id_tile2] += side_b
for tid, sides in matching_sides.items():
if len(sides) == 2:
corners[tid] = sides
assert len(corners) == 4
return corners
def rotate90(matrix: list) -> tuple:
"""return the matrix rotated by 90"""
return tuple(''.join(column)[::-1] for column in zip(*matrix))
def possible_orientations(matrix: list) -> list:
"""return all possible orientations of a given matrix"""
orientations = [matrix]
for _ in range(3):
matrix = rotate90(matrix)
orientations.append(matrix)
return orientations
def possible_arrangements(matrix: list) -> list:
"""return all possible arrangements of a given matrix"""
arrangements = possible_orientations(matrix)
arrangements.extend(possible_orientations(matrix[::-1]))
return arrangements
def strip_edges(matrix: list) -> list:
"""return a matrix without it's edges"""
return [row[1:-1] for row in matrix[1:-1]]
def matching_tile(tile: list, tiles: dict, side_a: str, side_b: str) -> list:
"""return the adjacent tile of a given one"""
edge_a = edge(tile, side_a)
for other_id, other_tile in tiles.items():
if tile is other_tile:
continue
for other_arr in possible_arrangements(other_tile):
if edge_a == edge(other_arr, side_b):
tiles.pop(other_id)
return other_arr
def matching_row(prev: list, tiles: dict, tiles_per_row: int) -> list:
"""return matching tiles in the row of a given one"""
matching_tiles = [prev]
tile = prev
for _ in range(tiles_per_row - 1):
tile = matching_tile(tile, tiles, 'e', 'w')
matching_tiles.append(tile)
return matching_tiles
def build_image(top_left_tile: list, tiles: dict, image_dimension: int) -> list:
"""build an image of a set of tiles"""
first = top_left_tile
image = []
while 1:
image_row = matching_row(first, tiles, image_dimension)
image_row = map(strip_edges, image_row)
image.extend(map(''.join, zip(*image_row)))
if not tiles:
break
first = matching_tile(first, tiles, 's', 'n')
return image
def count_pattern(image: list, pattern: tuple) -> int:
"""return the number of times a given pattern appears in an image"""
pattern_h, pattern_w = len(pattern), len(pattern[0])
image_sz = len(image)
deltas = []
for row_index, row in enumerate(pattern):
for cell_index, cell in enumerate(row):
if cell == '#':
deltas.append((row_index, cell_index))
for img in possible_arrangements(image):
appearances = 0
for row_index in range(image_sz - pattern_h):
for cell_index in range(image_sz - pattern_w):
if all(img[row_index + dr][cell_index + dc] == '#' for dr, dc in deltas):
appearances += 1
if appearances != 0:
return appearances
def part1(entries: dict) -> int:
"""part1 solver"""
corners = get_tiles_corners(entries)
corners_prod = 1
for tile_id in corners:
corners_prod *= tile_id
return corners_prod
def part2(entries: dict) -> int:
"""part2 solver"""
corners = get_tiles_corners(entries)
top_left_id, matching_sides = corners.popitem()
top_left = entries[top_left_id]
if matching_sides in ('ne', 'en'):
top_left = rotate90(top_left)
elif matching_sides in ('nw', 'wn'):
top_left = rotate90(rotate90(top_left))
elif matching_sides in ('sw', 'ws'):
top_left = rotate90(rotate90(rotate90(top_left)))
image_dimension = int(len(entries) ** 0.5)
entries.pop(top_left_id)
image = build_image(top_left, entries, image_dimension)
monster_cells = sum(row.count('#') for row in MONSTER_PATTERN)
water_cells = sum(row.count('#') for row in image)
n_monsters = count_pattern(image, MONSTER_PATTERN)
return water_cells - n_monsters * monster_cells
def test_input_day_20():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 20899048083289
assert part2(entries) == 273
def test_bench_day_20(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %d" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

110
2020/solutions/day_21.py Normal file
View File

@@ -0,0 +1,110 @@
"""AOC 2020 Day 21"""
import pathlib
import time
import collections
TEST_INPUT = """mxmxvkd kfcds sqjhc nhms (contains dairy, fish)
trh fvjkl sbzzf mxmxvkd (contains dairy)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd sbzzf (contains fish)"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
recipes = list()
ingredient_allergenes = collections.defaultdict(set)
recipes_with_allergene = collections.defaultdict(list)
for index, recipe in enumerate(input_data.split('\n')):
ingredients, allergenes = recipe.split(' (contains ')
ingredients = set(ingredients.split())
allergenes = set(allergenes.rstrip(')').split(', '))
recipes.append(ingredients)
for ingredient in ingredients:
ingredient_allergenes[ingredient] |= allergenes
for allergene in allergenes:
recipes_with_allergene[allergene].append(index)
return recipes, ingredient_allergenes, recipes_with_allergene
def find_safe_ingredients(recipes: list, ingredient_allergenes: dict, recipes_with_allergene: dict) -> list:
"""return a list of ingredients not containing allergenes"""
safe_ingredients = list()
for ingredient, allergenes in ingredient_allergenes.items():
impossible_allergenes = set()
for allergene in allergenes:
if any(ingredient not in recipes[i] for i in recipes_with_allergene[allergene]):
impossible_allergenes.add(allergene)
allergenes -= impossible_allergenes
if not allergenes:
safe_ingredients.append(ingredient)
return safe_ingredients
def assign_allergenes(ingredient_allergenes: dict) -> dict:
"""return a dict with each ingredient and its allergene"""
assigned_allergenes = dict()
while ingredient_allergenes:
assigned_allergene = str()
assigned_ingredient = str()
for ingredient, allergenes in ingredient_allergenes.items():
if len(allergenes) == 1:
assigned_allergene = allergenes.pop()
assigned_ingredient = ingredient
break
assigned_allergenes[assigned_allergene] = assigned_ingredient
ingredient_allergenes.pop(assigned_ingredient)
for ingredient, allergenes in ingredient_allergenes.items():
if assigned_allergene in allergenes:
allergenes.remove(assigned_allergene)
return assigned_allergenes
def part1(entries: tuple) -> int:
"""part1 solver"""
recipes, ingredient_allergenes, recipes_with_allergene = entries
safe_ingredients = find_safe_ingredients(recipes, ingredient_allergenes, recipes_with_allergene)
return sum(ingredient in recipe for recipe in recipes for ingredient in safe_ingredients)
def part2(entries: tuple) -> str:
"""part2 solver"""
recipes, ingredient_allergenes, recipes_with_allergene = entries
safe_ingredients = find_safe_ingredients(recipes, ingredient_allergenes, recipes_with_allergene)
for ingredient in safe_ingredients:
ingredient_allergenes.pop(ingredient)
assigned_allergenes = assign_allergenes(ingredient_allergenes)
return ','.join(map(assigned_allergenes.get, sorted(assigned_allergenes)))
def test_input_day_21():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 5
assert part2(entries) == 'mxmxvkd,sqjhc,fvjkl'
def test_bench_day_21(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %s" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

110
2020/solutions/day_22.py Normal file
View File

@@ -0,0 +1,110 @@
"""AOC 2020 Day 22"""
import pathlib
import time
import collections
import itertools
TEST_INPUT = """Player 1:
9
2
6
3
1
Player 2:
5
8
4
7
10"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
deck1, deck2 = input_data.split('\n\n')
deck1, deck2 = deck1.splitlines(), deck2.splitlines()
deck1 = collections.deque(map(int, deck1[1:]))
deck2 = collections.deque(map(int, deck2[1:]))
return deck1, deck2
def play_space_cards(deck1: collections.deque, deck2: collections.deque) -> collections.deque:
"""return the winner's deck after a space cards game"""
assert len(deck1) == len(deck2)
while deck1 and deck2:
card1, card2 = deck1.popleft(), deck2.popleft()
assert card1 != card2
if card1 > card2:
deck1.extend((card1, card2))
else:
deck2.extend((card2, card1))
return deck1 if deck1 else deck2
def play_recursive_space_cards(deck1: collections.deque, deck2: collections.deque) -> tuple:
"""return the winner and its deck after a recursive space cards game"""
configurations = set()
while deck1 and deck2:
current_config = (tuple(deck1), tuple(deck2))
if current_config in configurations:
return 1, deck1
configurations.add(current_config)
card1, card2 = deck1.popleft(), deck2.popleft()
assert card1 != card2
if card1 <= len(deck1) and card2 <= len(deck2):
sub_deck1 = collections.deque(itertools.islice(deck1, card1))
sub_deck2 = collections.deque(itertools.islice(deck2, card2))
winner, _ = play_recursive_space_cards(sub_deck1, sub_deck2)
else:
winner = 1 if card1 > card2 else 2
if winner == 1:
deck1.extend((card1, card2))
else:
deck2.extend((card2, card1))
return (1, deck1) if deck1 else (2, deck2)
def part1(entries: tuple) -> int:
"""part1 solver"""
deck1, deck2 = entries[0].copy(), entries[1].copy()
winner = play_space_cards(deck1, deck2)
return sum(index * card for index, card in enumerate(reversed(winner), 1))
def part2(entries: tuple) -> str:
"""part2 solver"""
deck1, deck2 = entries
_, winner = play_recursive_space_cards(deck1, deck2)
return sum(index * card for index, card in enumerate(reversed(winner), 1))
def test_input_day_22():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 306
assert part2(entries) == 291
def test_bench_day_22(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %d" % part1(entries))
print("Part 2: %s" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

101
2020/solutions/day_23.py Normal file
View File

@@ -0,0 +1,101 @@
"""AOC 2020 Day 23"""
import pathlib
import time
TEST_INPUT = """389125467"""
class Cup:
"""a cup with a value and a pointer to the next cup"""
def __init__(self, value):
self.value = value
self.next = None
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> list:
"""take input data and return the appropriate data structure"""
return list(map(int, input_data))
def build_list(values: list, n_cups: int = None) -> tuple:
"""return the value of the first cup and a dict of Cup"""
if n_cups is None:
n_cups = len(values)
cups_list = [None]*(n_cups+1)
for i in range(1, n_cups+1):
cups_list[i] = Cup(i)
for i, _ in enumerate(values):
cups_list[values[i]].next = cups_list[values[(i+1)%len(values)]]
return values[0], cups_list
def play_crab_cups(first_cup: int, cups: list, moves: int):
"""play n moves of crab cups"""
current = cups[first_cup]
max_cup = len(cups) - 1
for _ in range(moves):
next_cup = current.next
picked = (next_cup.value, next_cup.next.value, next_cup.next.next.value)
current.next = current.next.next.next.next
label = current.value
while label in picked or label == current.value:
if label != 1:
label = label-1
else:
label = max_cup
destination = cups[label]
next_cup.next.next.next = destination.next
destination.next = next_cup
current = current.next
def part1(entries: list) -> str:
"""part1 solver"""
first_cup, cups = build_list(entries)
play_crab_cups(first_cup, cups, 100)
cur_cup = cups[1]
cups_str = ''
for _ in range(8):
cur_cup = cur_cup.next
cups_str += str(cur_cup.value)
return cups_str
def part2(entries: list) -> int:
"""part2 solver"""
entries.extend(range(10, 1000001))
first_cup, cups = build_list(entries)
play_crab_cups(first_cup, cups, 10000000)
return cups[1].next.value * cups[1].next.next.value
def test_input_day_23():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == '67384529'
assert part2(entries) == 149245887792
def test_bench_day_23(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %s" % part1(entries))
print("Part 2: %s" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

144
2020/solutions/day_24.py Normal file
View File

@@ -0,0 +1,144 @@
"""AOC 2020 Day 24"""
import pathlib
import time
import re
import itertools
TEST_INPUT = """sesenwnenenewseeswwswswwnenewsewsw
neeenesenwnwwswnenewnwwsewnenwseswesw
seswneswswsenwwnwse
nwnwneseeswswnenewneswwnewseswneseene
swweswneswnenwsewnwneneseenw
eesenwseswswnenwswnwnwsewwnwsene
sewnenenenesenwsewnenwwwse
wenwwweseeeweswwwnwwe
wsweesenenewnwwnwsenewsenwwsesesenwne
neeswseenwwswnwswswnw
nenwswwsewswnenenewsenwsenwnesesenew
enewnwewneswsewnwswenweswnenwsenwsw
sweneswneswneneenwnewenewwneswswnese
swwesenesewenwneswnwwneseswwne
enesenwswwswneneswsenwnewswseenwsese
wnwnesenesenenwwnenwsewesewsesesew
nenewswnwewswnenesenwnesewesw
eneswnwswnwsenenwnwnwwseeswneewsenese
neswnwewnwnwseenwseesewsenwsweewe
wseweeenwnesenwwwswnew"""
STEPMAP = {
'e': (1, 0),
'se': (1, 1),
'sw': (0, 1),
'w': (-1, 0),
'nw': (-1, -1),
'ne': (0, -1)
}
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> set:
"""take input data and return the appropriate data structure"""
rexp_steps = re.compile(r'e|se|sw|w|nw|ne')
tiles_steps = [rexp_steps.findall(line) for line in input_data.split('\n')]
black_tiles = set()
for steps in tiles_steps:
dst_tile = find_dst_tile(steps)
if dst_tile in black_tiles:
black_tiles.remove(dst_tile)
else:
black_tiles.add(dst_tile)
return black_tiles
def find_dst_tile(steps: list) -> tuple:
"""calculate the destination tile based on the steps"""
x_dst, y_dst = 0, 0
for step in steps:
d_x, d_y = STEPMAP[step]
x_dst += d_x
y_dst += d_y
return x_dst, y_dst
def count_black_neighbors(tiles: set, x_tile: int, y_tile: int) -> int:
"""return the number of black adjacent tile of a given one"""
black_neighbors = 0
for d_x, d_y in STEPMAP.values():
if (x_tile+d_x, y_tile+d_y) in tiles:
black_neighbors += 1
return black_neighbors
def calculate_floor_bounds(tiles: set) -> tuple:
"""return the floor boundaries"""
min_x = float('Inf')
max_x = float('-Inf')
min_y = float('Inf')
max_y = float('-Inf')
for x_tile, y_tile in tiles:
if x_tile < min_x:
min_x = x_tile
elif x_tile > max_x:
max_x = x_tile
if y_tile < min_y:
min_y = y_tile
elif y_tile > max_y:
max_y = y_tile
return range(min_x-1, max_x+2), range(min_y-1, max_y+2)
def flip_tiles(tiles: set) -> set:
"""calculate the new daily floor"""
new_floor = set()
for tile in itertools.product(*calculate_floor_bounds(tiles)):
black_neighbors = count_black_neighbors(tiles, *tile)
if tile in tiles and not (black_neighbors == 0 or black_neighbors > 2):
new_floor.add(tile)
elif tile not in tiles and black_neighbors == 2:
new_floor.add(tile)
return new_floor
def part1(entries: set) -> int:
"""part1 solver"""
return len(entries)
def part2(entries: list) -> int:
"""part2 solver"""
for _ in range(100):
entries = flip_tiles(entries)
return len(entries)
def test_input_day_24():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 10
assert part2(entries) == 2208
def test_bench_day_24(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %s" % part1(entries))
print("Part 2: %s" % part2(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()

52
2020/solutions/day_25.py Normal file
View File

@@ -0,0 +1,52 @@
"""AOC 2020 Day 25"""
import pathlib
import time
TEST_INPUT = """5764801
17807724"""
def read_input(input_path: str) -> str:
"""take input file path and return a str with the file's content"""
with open(input_path, 'r') as input_file:
input_data = input_file.read().strip()
return input_data
def extract(input_data: str) -> tuple:
"""take input data and return the appropriate data structure"""
return tuple(map(int, input_data.splitlines()))
def part1(entries: tuple) -> int:
"""part1 solver"""
card_key, door_key = entries
loop_size = 0
key = 1
while key not in (card_key, door_key):
loop_size += 1
key = (key * 7) % 20201227
if key == card_key:
return pow(door_key, loop_size, 20201227)
return pow(card_key, loop_size, 20201227)
def test_input_day_25():
"""pytest testing function"""
entries = extract(TEST_INPUT)
assert part1(entries) == 14897079
def test_bench_day_25(benchmark):
"""pytest-benchmark function"""
benchmark(main)
def main():
"""main function"""
input_path = str(pathlib.Path(__file__).resolve().parent.parent) + "/inputs/" + str(pathlib.Path(__file__).stem)
start_time = time.time()
input_data = read_input(input_path)
entries = extract(input_data)
print("Part 1: %s" % part1(entries))
end_time = time.time()
print("Execution time: %f" % (end_time-start_time))
if __name__ == "__main__":
main()