mirror of
https://github.com/Noettore/AdventOfCode.git
synced 2025-10-15 11:46:39 +02:00
Restyled README and file structure
Signed-off-by: Ettore Dreucci <ettore.dreucci@gmail.com>
This commit is contained in:
44
2020/solutions/day_01.py
Normal file
44
2020/solutions/day_01.py
Normal 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
49
2020/solutions/day_02.py
Normal 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
59
2020/solutions/day_03.py
Normal 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
72
2020/solutions/day_04.py
Normal 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
39
2020/solutions/day_05.py
Normal 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
74
2020/solutions/day_06.py
Normal 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
103
2020/solutions/day_07.py
Normal 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
106
2020/solutions/day_08.py
Normal 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
87
2020/solutions/day_09.py
Normal 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
111
2020/solutions/day_10.py
Normal 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
143
2020/solutions/day_11.py
Normal 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
116
2020/solutions/day_12.py
Normal 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
87
2020/solutions/day_13.py
Normal 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
109
2020/solutions/day_14.py
Normal 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
68
2020/solutions/day_15.py
Normal 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
136
2020/solutions/day_16.py
Normal 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
95
2020/solutions/day_17.py
Normal 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
125
2020/solutions/day_18.py
Normal 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
153
2020/solutions/day_19.py
Normal 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
306
2020/solutions/day_20.py
Normal 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
110
2020/solutions/day_21.py
Normal 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
110
2020/solutions/day_22.py
Normal 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
101
2020/solutions/day_23.py
Normal 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
144
2020/solutions/day_24.py
Normal 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
52
2020/solutions/day_25.py
Normal 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()
|
Reference in New Issue
Block a user