mirror of
https://github.com/thib8956/advent-of-code.git
synced 2025-08-24 00:11:57 +00:00
chore: create new project structure and aoc.py runner script
This commit is contained in:
24
adventofcode/2020/day1/day1.py
Normal file
24
adventofcode/2020/day1/day1.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#! /usr/bin/env python3
|
||||
from itertools import product
|
||||
|
||||
|
||||
def part1(inp):
|
||||
inp = [int(x) for x in inp]
|
||||
result_pairs = [x for x in list(product(inp, inp)) if sum(x) == 2020]
|
||||
print(result_pairs)
|
||||
print(result_pairs[0][0] * result_pairs[0][1])
|
||||
|
||||
|
||||
def part2(inp):
|
||||
inp = [int(x) for x in inp]
|
||||
result_pairs = [x for x in list(product(inp, repeat=3)) if sum(x) == 2020]
|
||||
print(result_pairs)
|
||||
print(result_pairs[0][0] * result_pairs[0][1] * result_pairs[0][2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
lines = [x for x in fileinput.input()]
|
||||
part1(lines)
|
||||
part2(lines)
|
||||
|
30
adventofcode/2020/day10/day10.py
Normal file
30
adventofcode/2020/day10/day10.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def part1(adapters):
|
||||
counts = Counter()
|
||||
# 1 for the socket, of 3 for the device
|
||||
for current, next in zip([0] + adapters, adapters + [3]):
|
||||
counts[next - current] += 1
|
||||
return counts[1] * counts[3]
|
||||
|
||||
|
||||
def part2(adapters):
|
||||
counts = Counter({0: 1})
|
||||
for jolt in adapters:
|
||||
s = counts[jolt - 1] + counts[jolt - 2] + counts[jolt - 3]
|
||||
counts[jolt] = s
|
||||
return max(counts.values())
|
||||
|
||||
|
||||
def main(f):
|
||||
adapters = sorted(int(l.rstrip()) for l in f)
|
||||
print(part1(adapters))
|
||||
print(part2(adapters))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
||||
|
34
adventofcode/2020/day11/day11.py
Normal file
34
adventofcode/2020/day11/day11.py
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
from rules import part1_rules, part2_rules
|
||||
|
||||
|
||||
def main(grid, rules):
|
||||
generation = 0
|
||||
while True:
|
||||
changes, next_grid = step(grid, rules)
|
||||
generation += 1
|
||||
grid = next_grid
|
||||
assert generation < 1000
|
||||
if generation % 10 == 0:
|
||||
print(f"Generation {generation}, changes: {changes}")
|
||||
if changes == 0:
|
||||
return next_grid.count("#")
|
||||
|
||||
|
||||
def step(grid, rules):
|
||||
changes = 0
|
||||
next_grid = grid[:]
|
||||
for index, cell in enumerate(grid):
|
||||
try:
|
||||
changes += rules[cell](index, grid, next_grid)
|
||||
except KeyError:
|
||||
pass
|
||||
return changes, next_grid
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
with open(sys.argv[1]) as infile:
|
||||
grid = list("".join(infile.read().splitlines()))
|
||||
print("Part 1 ", main(grid, rules=part1_rules))
|
||||
print("Part 2 ", main(grid, rules=part2_rules))
|
28
adventofcode/2020/day11/grid.py
Normal file
28
adventofcode/2020/day11/grid.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
class Grid:
|
||||
def __init__(self, inp):
|
||||
lines = inp.read().splitlines()
|
||||
self.cells = list("".join(lines))
|
||||
self.height = len(lines)
|
||||
self.width = len(lines[0])
|
||||
|
||||
def __getitem__(self, key):
|
||||
x, y = key
|
||||
return cells[y * self.width + x]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
x, y = key
|
||||
cells[y * self.width + x] = value
|
||||
|
||||
def __iter__(self):
|
||||
return self.cells.__iter__()
|
||||
|
||||
def __str__(self):
|
||||
"\n".join(
|
||||
"".join(
|
||||
grid[pos : pos + grid_width]
|
||||
for pos in range(0, self.width, self.height)
|
||||
)
|
||||
)
|
117
adventofcode/2020/day11/rules.py
Normal file
117
adventofcode/2020/day11/rules.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
from itertools import count, takewhile
|
||||
|
||||
directions = (
|
||||
(-1, -1),
|
||||
(-1, 0),
|
||||
(-1, 1),
|
||||
(0, -1),
|
||||
(0, 1),
|
||||
(1, -1),
|
||||
(1, 0),
|
||||
(1, 1),
|
||||
)
|
||||
|
||||
grid_width = 98
|
||||
grid_height = 97
|
||||
|
||||
|
||||
def handle_empty(index, grid, next_grid):
|
||||
"""
|
||||
If a seat is empty (L) and there are no occupied seats adjacent to it, the seat becomes occupied.
|
||||
"""
|
||||
neighbors = count_neighbors(index, grid)
|
||||
if neighbors == 0:
|
||||
next_grid[index] = "#"
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def handle_occupied(index, grid, next_grid):
|
||||
"""
|
||||
If a seat is occupied (#) and four or more seats adjacent to it are also occupied, the seat becomes empty.
|
||||
"""
|
||||
neighbors = count_neighbors(index, grid)
|
||||
if neighbors >= 4:
|
||||
next_grid[index] = "L"
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def count_neighbors(pos, grid):
|
||||
neighbors = 0
|
||||
x = pos % grid_width
|
||||
y = pos // grid_width
|
||||
for (dx, dy) in directions:
|
||||
xx = x + dx
|
||||
yy = y + dy
|
||||
if not in_bounds((xx, yy)):
|
||||
continue
|
||||
|
||||
if grid[yy * grid_width + xx] == "#":
|
||||
neighbors += 1
|
||||
return neighbors
|
||||
|
||||
|
||||
def handle_empty_2(index, grid, next_grid):
|
||||
"""
|
||||
If a seat is empty and there are no occupied seat visible in neither direction,
|
||||
the seat becomes occupied
|
||||
"""
|
||||
neighbors = 0
|
||||
x = index % grid_width
|
||||
y = index // grid_width
|
||||
for direction in directions:
|
||||
# keep moving in the specified direction, while checking
|
||||
# that we are in bounds of the grid
|
||||
for xx, yy in takewhile(in_bounds, move(x, y, direction)):
|
||||
cell = grid[yy * grid_width + xx]
|
||||
if cell == "#":
|
||||
neighbors += 1
|
||||
elif cell == "L":
|
||||
break # No occupied seat in that direction, we can break
|
||||
|
||||
if neighbors == 0:
|
||||
next_grid[index] = "#"
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def handle_occupied_2(index, grid, next_grid):
|
||||
"""
|
||||
An occupied seat becomes empty if there are five or more visible occupied
|
||||
seats in either direction.
|
||||
"""
|
||||
occupied = 0
|
||||
x = index % grid_width
|
||||
y = index // grid_width
|
||||
for direction in directions:
|
||||
for xx, yy in takewhile(in_bounds, move(x, y, direction)):
|
||||
cell = grid[yy * grid_width + xx]
|
||||
if cell == "#":
|
||||
occupied += 1
|
||||
elif cell != ".":
|
||||
break
|
||||
|
||||
if occupied >= 5:
|
||||
next_grid[index] = "L"
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def in_bounds(pos):
|
||||
x, y = pos
|
||||
return 0 <= x < grid_width and 0 <= y < grid_height
|
||||
|
||||
|
||||
def move(x, y, direction):
|
||||
xx = x
|
||||
yy = y
|
||||
while True:
|
||||
yield xx, yy
|
||||
xx += direction[0]
|
||||
yy += direction[1]
|
||||
|
||||
|
||||
part1_rules = {"L": handle_empty, "#": handle_occupied}
|
||||
part2_rules = {"L": handle_empty_2, "#": handle_occupied_2}
|
58
adventofcode/2020/day14/day14.py
Normal file
58
adventofcode/2020/day14/day14.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def part1(infile):
|
||||
memory = defaultdict(int)
|
||||
for line in infile:
|
||||
left, right = line.split("=")
|
||||
if left.startswith("mask"):
|
||||
one_mask = right.translate(right.maketrans("X", "0"))
|
||||
zero_mask = right.translate(right.maketrans("X10", "001"))
|
||||
else:
|
||||
address = int(left.split("[")[1].rstrip("] ")) # mem[42] -> 42
|
||||
value = int(right.rstrip())
|
||||
memory[address] = value & ~int(zero_mask, 2) | int(one_mask, 2)
|
||||
|
||||
return sum(memory.values())
|
||||
|
||||
|
||||
def part2(infile):
|
||||
memory = defaultdict(int)
|
||||
for line in infile:
|
||||
left, right = line.split(" = ")
|
||||
if left.startswith("mask"):
|
||||
mask = right.rstrip()
|
||||
else:
|
||||
value = right.rstrip()
|
||||
address = apply_mask(left.split("[")[1].rstrip("] "), mask)
|
||||
for addr in generate_floating_addresses(address):
|
||||
memory[int(addr, 2)] = int(value)
|
||||
return sum(memory.values())
|
||||
|
||||
|
||||
def apply_mask(address, mask):
|
||||
address = bin(int(address)).lstrip("0b")
|
||||
address = address.zfill(36)
|
||||
for index, bit in enumerate(mask):
|
||||
if bit == "1":
|
||||
address = address[:index] + "1" + address[index + 1 :]
|
||||
elif bit == "X":
|
||||
address = address[:index] + "X" + address[index + 1 :]
|
||||
return address
|
||||
|
||||
|
||||
def generate_floating_addresses(address):
|
||||
index = address.find("X")
|
||||
if index == -1:
|
||||
return [address]
|
||||
a1 = generate_floating_addresses(address[:index] + "0" + address[index + 1 :])
|
||||
a2 = generate_floating_addresses(address[:index] + "1" + address[index + 1 :])
|
||||
return a1 + a2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
lines = [x for x in fileinput.input()]
|
||||
print(part1(lines))
|
||||
print(part2(lines))
|
58
adventofcode/2020/day15/day15.py
Normal file
58
adventofcode/2020/day15/day15.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
from collections import defaultdict
|
||||
from array import array
|
||||
|
||||
|
||||
def main(initial_suite, max_iteration):
|
||||
iteration = 1
|
||||
seen = defaultdict(int)
|
||||
|
||||
# init
|
||||
for number in initial_suite:
|
||||
seen[number] = iteration
|
||||
iteration += 1
|
||||
|
||||
current = 0
|
||||
while iteration < max_iteration:
|
||||
last_seen = seen[current]
|
||||
if last_seen == 0:
|
||||
seen[current] = iteration
|
||||
current = 0 # next
|
||||
else:
|
||||
seen[current] = iteration
|
||||
current = iteration - last_seen # next
|
||||
iteration += 1
|
||||
return current
|
||||
|
||||
|
||||
def main_array(initial_suite, max_iteration):
|
||||
iteration = 1
|
||||
seen = array('I', [0] * max_iteration)
|
||||
|
||||
# init
|
||||
for number in initial_suite:
|
||||
seen[number] = iteration
|
||||
iteration += 1
|
||||
|
||||
current = 0
|
||||
while iteration < max_iteration:
|
||||
last_seen = seen[current]
|
||||
if last_seen == 0:
|
||||
seen[current] = iteration
|
||||
current = 0 # next
|
||||
else:
|
||||
seen[current] = iteration
|
||||
current = iteration - last_seen # next
|
||||
iteration += 1
|
||||
return current
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
inp = [6, 3, 15, 13, 1, 0]
|
||||
# 423 µs ± 53.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
|
||||
print(main(inp, 2020))
|
||||
# 13.6 s ± 2.89 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
|
||||
#print(main(inp, 30000000))
|
||||
print(main_array(inp, 30000000))
|
108
adventofcode/2020/day16/day16.py
Normal file
108
adventofcode/2020/day16/day16.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# /usr/bin/env python3
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
"""
|
||||
identify invalid nearby tickets by considering only whether tickets contain values that are not valid for any field.
|
||||
"""
|
||||
|
||||
|
||||
def main(content):
|
||||
rules, my_ticket, other_tickets = content.split("\n\n")
|
||||
rules = parse_fields(rules)
|
||||
my_ticket = my_ticket.splitlines()[1]
|
||||
other_tickets = other_tickets.splitlines()[1:]
|
||||
print("Ticket scanning error rate ", part1(other_tickets, rules))
|
||||
part2(my_ticket, other_tickets, rules)
|
||||
|
||||
|
||||
def parse_fields(fields):
|
||||
fields_dict = {}
|
||||
for field in fields.splitlines():
|
||||
k, v = field.split(": ")
|
||||
ranges = re.findall(r"(\d+)-(\d+)", v)
|
||||
fields_dict[k] = [range(int(r[0]), int(r[1]) + 1) for r in ranges]
|
||||
return fields_dict
|
||||
|
||||
|
||||
def part1(tickets, rules):
|
||||
scanning_error_rate = 0
|
||||
for ticket in tickets:
|
||||
scanning_error_rate += sum(validate_ticket(ticket, rules))
|
||||
return scanning_error_rate
|
||||
|
||||
|
||||
def validate_ticket(ticket, rules):
|
||||
invalid_fields = []
|
||||
for value in ticket.split(","):
|
||||
value = int(value)
|
||||
if not validate_field(value, *rules.values()):
|
||||
invalid_fields.append(value)
|
||||
return invalid_fields
|
||||
|
||||
|
||||
def validate_field(field, *rules):
|
||||
validations = (any(field in r for r in rule) for rule in rules)
|
||||
return any(validations)
|
||||
|
||||
|
||||
def part2(my_ticket, other_tickets, rules):
|
||||
# filter only valid tickets
|
||||
valid_tickets = [ticket for ticket in other_tickets if validate_ticket(ticket, rules) == []]
|
||||
valid_tickets.append(my_ticket) # my ticket is valid
|
||||
|
||||
# possible field for each index of a ticket
|
||||
candidates = defaultdict(set)
|
||||
for index in range(len(rules)):
|
||||
def inner():
|
||||
for rule_name, constraints in rules.items():
|
||||
for ticket in valid_tickets:
|
||||
field_value = int(ticket.split(",")[index])
|
||||
if not validate_field(field_value, constraints):
|
||||
return
|
||||
candidates[index].add(rule_name)
|
||||
inner()
|
||||
|
||||
sorted_candidates = sort_candidates(candidates)
|
||||
|
||||
fields_indexes = {}
|
||||
try:
|
||||
while len(fields_indexes) != len(rules):
|
||||
index, found = sorted_candidates.popitem()
|
||||
found = next(iter(found))
|
||||
fields_indexes[index] = found
|
||||
sorted_candidates = remove_item(sorted_candidates, found)
|
||||
except:
|
||||
pass
|
||||
|
||||
fields_indexes = {k: v for k,v in fields_indexes.items() if v.startswith('departure')}
|
||||
|
||||
total = 1
|
||||
my_ticket = my_ticket.split(',')
|
||||
for index in fields_indexes:
|
||||
total *= int(my_ticket[index])
|
||||
a = 1
|
||||
|
||||
|
||||
def sort_candidates(c):
|
||||
return {x: c[x] for x in sorted(c, key=lambda k: len(c[k]), reverse=True)}
|
||||
|
||||
def remove_item(candidates, item):
|
||||
ret = {}
|
||||
for key, value in candidates.items():
|
||||
try:
|
||||
value.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
ret[key] = value
|
||||
|
||||
#candidates = {k: set(v - item) for k,v in candidates.items()}
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
with open(sys.argv[1]) as f:
|
||||
main(f.read())
|
||||
|
14
adventofcode/2020/day2/day2.py
Normal file
14
adventofcode/2020/day2/day2.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import part1
|
||||
import part2
|
||||
|
||||
|
||||
def main(lines):
|
||||
part1.main(lines)
|
||||
part2.main(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
lines = [x for x in fileinput.input()]
|
||||
main(lines)
|
||||
|
24
adventofcode/2020/day2/part1.py
Normal file
24
adventofcode/2020/day2/part1.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
|
||||
def parse_line(line):
|
||||
repeat_range, letter, pwd = line.split(' ')
|
||||
letter = letter[0]
|
||||
repeat_min, repeat_max = repeat_range.split('-')
|
||||
repeat_min, repeat_max = int(repeat_min), int(repeat_max)
|
||||
return letter, range(repeat_min, repeat_max + 1), pwd
|
||||
|
||||
|
||||
def test_password(line):
|
||||
letter, repeat_range, pwd = parse_line(line)
|
||||
count = pwd.count(letter)
|
||||
return count in repeat_range
|
||||
|
||||
|
||||
def main(passwds):
|
||||
valid_counter = 0
|
||||
for l in passwds:
|
||||
if test_password(l):
|
||||
valid_counter += 1
|
||||
print(f"Number of valid password in input : {valid_counter}")
|
||||
|
28
adventofcode/2020/day2/part2.py
Normal file
28
adventofcode/2020/day2/part2.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
|
||||
def xor(a, b):
|
||||
return (a and not b) or (not a and b)
|
||||
|
||||
|
||||
def parse_line(line):
|
||||
repeat_range, letter, pwd = line.split(' ')
|
||||
letter = letter[0]
|
||||
first_pos, second_pos = repeat_range.split('-')
|
||||
first_pos, second_pos = int(first_pos), int(second_pos)
|
||||
return letter, first_pos, second_pos, pwd
|
||||
|
||||
|
||||
def test_password(line):
|
||||
letter, first_pos, second_pos, pwd = parse_line(line)
|
||||
return xor(pwd[first_pos - 1] == letter,
|
||||
pwd[second_pos - 1] == letter)
|
||||
|
||||
|
||||
def main(passwds):
|
||||
valid_counter = 0
|
||||
for l in passwds:
|
||||
if test_password(l):
|
||||
valid_counter += 1
|
||||
print(f"Number of valid password in input : {valid_counter}")
|
||||
|
42
adventofcode/2020/day3/day3.py
Normal file
42
adventofcode/2020/day3/day3.py
Normal file
@@ -0,0 +1,42 @@
|
||||
def check_slope_for_trees(plan, x_right=3, y_down=1):
|
||||
x_max = len(plan[0])
|
||||
y_max = len(plan)
|
||||
|
||||
x, y = 0, 0
|
||||
tree_count = 0
|
||||
while y < y_max - 1:
|
||||
x += x_right
|
||||
x %= x_max # wrap x
|
||||
y += y_down
|
||||
|
||||
pos = plan[y][x]
|
||||
if pos == '#':
|
||||
tree_count += 1
|
||||
return tree_count
|
||||
|
||||
|
||||
def part1(plan):
|
||||
tree_count = check_slope_for_trees(plan)
|
||||
print(f"number of trees : {tree_count}")
|
||||
|
||||
|
||||
def part2(plan):
|
||||
slopes = [{"x": 1, "y": 1}, {"x": 3, "y": 1}, {"x": 5, "y": 1}, {"x": 7, "y": 1}, {"x": 1, "y": 2}]
|
||||
tree_product = 1
|
||||
for slope in slopes:
|
||||
tree_count = check_slope_for_trees(plan, slope["x"], slope["y"])
|
||||
tree_product *= tree_count
|
||||
print(f"slope {slope} number of trees : {tree_count}")
|
||||
print(f"Cumulative product of tress : {tree_product}")
|
||||
|
||||
|
||||
def main(f):
|
||||
lines = [line.rstrip() for line in f]
|
||||
part1(lines)
|
||||
part2(lines)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
||||
|
52
adventofcode/2020/day4/day4.py
Normal file
52
adventofcode/2020/day4/day4.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#! /usr/bin/env python3
|
||||
import re
|
||||
|
||||
|
||||
def main(inp):
|
||||
inp = open(inp).read().rstrip().split('\n\n')
|
||||
valid_passeports_1, valid_passeports_2 = 0, 0
|
||||
for l in inp:
|
||||
l = re.split(r'[\n\s]', l)
|
||||
passeport = dict(p.split(':') for p in l)
|
||||
if check_passeport(passeport):
|
||||
valid_passeports_1 += 1
|
||||
if check_passeport(passeport, run_validators=True):
|
||||
valid_passeports_2 += 1
|
||||
print("Part 1: valid passeports: ", valid_passeports_1)
|
||||
print("Part 2: valid passeports: ", valid_passeports_2)
|
||||
|
||||
|
||||
def check_passeport(passeport, run_validators=False):
|
||||
fields = [
|
||||
('byr', lambda v: 1920 <= int(v) <= 2002), # (Birth Year)
|
||||
('iyr', lambda v: 2010 <= int(v) <= 2020), # (Issue Year)
|
||||
('eyr', lambda v: 2020 <= int(v) <= 2030), # (Expiration Year)
|
||||
('hgt', validate_height), # (Height)
|
||||
('hcl', lambda v: re.search("#[0-9a-f]{6}", v)), # (Hair Color)
|
||||
('ecl', lambda v: v in ('amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth')), # (Eye Color)
|
||||
('pid', lambda v: len(v) == 9 and v.isdecimal()), # (Passport ID)
|
||||
#'cid' # Country id, ignored
|
||||
]
|
||||
for field, validator in fields:
|
||||
value = passeport.get(field)
|
||||
if value is None:
|
||||
return False
|
||||
elif run_validators and not validator(value):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def validate_height(v):
|
||||
unit = v[-2:]
|
||||
height = int(v[:-2])
|
||||
if unit == 'cm':
|
||||
return 150 <= height <= 193
|
||||
if unit == 'in':
|
||||
return 59 <= height <= 76
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
main(sys.argv[1])
|
||||
|
68
adventofcode/2020/day5/day5.py
Normal file
68
adventofcode/2020/day5/day5.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#! /usr/bin/env python3
|
||||
from itertools import product
|
||||
from bisect import bisect
|
||||
|
||||
|
||||
def main(inp):
|
||||
results = {}
|
||||
for line in inp:
|
||||
boarding_pass = line.rstrip()
|
||||
row, col = parse_boarding_pass(boarding_pass)
|
||||
seat_id = get_seat_id(row, col)
|
||||
results[boarding_pass] = (row, col, seat_id)
|
||||
part1(results)
|
||||
part2(results)
|
||||
|
||||
|
||||
def part1(results):
|
||||
max_seat_id = max(x[2] for x in results.values())
|
||||
print("Max seat ID: ", max_seat_id)
|
||||
|
||||
|
||||
def part2(results):
|
||||
seat_ids = sorted(x[2] for x in results.values())
|
||||
missing_seat_ids = set(range(max(seat_ids))) - set(seat_ids)
|
||||
print("Your seat id : ", max(missing_seat_ids))
|
||||
|
||||
|
||||
def parse_boarding_pass(boarding_pass, strategy="binary"):
|
||||
"Poor man's dispatcher"
|
||||
try:
|
||||
to_call = globals()[f"parse_boarding_pass_{strategy}"]
|
||||
return to_call(boarding_pass)
|
||||
except KeyError:
|
||||
raise KeyError(f"Bad strategy name {strategy}")
|
||||
|
||||
|
||||
def parse_boarding_pass_binary(boarding_pass):
|
||||
"Parse boarding pass using a binary conversion"
|
||||
boarding_pass = boarding_pass.translate(str.maketrans("FLBR", "0011"))
|
||||
row = boarding_pass[:7]
|
||||
col = boarding_pass[7:]
|
||||
return int(row, base=2), int(col, base=2)
|
||||
|
||||
|
||||
def parse_boarding_pass_bisect(boarding_pass):
|
||||
"Pass boarding pass using bisection algorithm"
|
||||
row = bisect(boarding_pass[:7], lower_option="F", upper_option="B", max=127)
|
||||
col = bisect(boarding_pass[7:], lower_option="L", upper_option="R", max=7)
|
||||
return row, col
|
||||
|
||||
|
||||
def bisect(inp, lower_option, upper_option, max):
|
||||
min_v, max_v = 0, max
|
||||
for l in inp:
|
||||
length = max_v - min_v
|
||||
if l == lower_option:
|
||||
max_v = min_v + length // 2
|
||||
elif l == upper_option:
|
||||
min_v = 1 + min_v + length // 2
|
||||
return min_v
|
||||
|
||||
def get_seat_id(row, col):
|
||||
return 8 * row + col
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
27
adventofcode/2020/day5/tests.py
Normal file
27
adventofcode/2020/day5/tests.py
Normal file
@@ -0,0 +1,27 @@
|
||||
#! /usr/bin/env python3
|
||||
from day5 import *
|
||||
|
||||
def tests():
|
||||
inputs = {
|
||||
"FBFBBFFRLR": (44, 5, 357),
|
||||
"BFFFBBFRRR": (70, 7, 567),
|
||||
"FFFBBBFRRR": (14, 7, 119),
|
||||
"BBFFBBFRLL": (102, 4, 820)
|
||||
}
|
||||
|
||||
test("bisect", inputs)
|
||||
test("binary", inputs)
|
||||
|
||||
|
||||
def test(strategy, inputs):
|
||||
for boarding_pass, expected in inputs.items():
|
||||
row, col = parse_boarding_pass(boarding_pass, strategy=strategy)
|
||||
seat_id = get_seat_id(row, col)
|
||||
assert row == expected[0]
|
||||
assert col == expected[1]
|
||||
assert seat_id == expected[2]
|
||||
print(row, col, seat_id, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests()
|
30
adventofcode/2020/day6/day6.py
Normal file
30
adventofcode/2020/day6/day6.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#! /usr/bin/env python3
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def part1(groups):
|
||||
number_of_questions = 0
|
||||
for group in groups:
|
||||
unique_questions = set(group.replace("\n", ""))
|
||||
number_of_questions += len(unique_questions)
|
||||
print(number_of_questions)
|
||||
|
||||
|
||||
def part2(groups):
|
||||
# number of questions for which everyone in a group answered 'yes'
|
||||
number_of_questions = 0
|
||||
for group in groups:
|
||||
group_length = group.count("\n") + 1
|
||||
group_counter = Counter(group.replace("\n", ""))
|
||||
everyone_answered = [k for (k, v) in group_counter.items() if v == group_length]
|
||||
number_of_questions += len(everyone_answered)
|
||||
print(number_of_questions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
inp = sys.argv[1]
|
||||
groups = open(inp).read().split("\n\n")
|
||||
part1(groups)
|
||||
part2(groups)
|
||||
|
47
adventofcode/2020/day7/day7.py
Normal file
47
adventofcode/2020/day7/day7.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
from collections import defaultdict, deque
|
||||
|
||||
|
||||
def main(input_rules):
|
||||
rules = parse_rules(input_rules)
|
||||
reverse_rules = build_reverse_rules(rules)
|
||||
print(part1(reverse_rules))
|
||||
print(part2(rules, "shiny gold"))
|
||||
|
||||
|
||||
def parse_rules(input_rules):
|
||||
rules = {}
|
||||
for input_rule in input_rules:
|
||||
color, rule = input_rule.split(" bags contain ")
|
||||
rules[color] = {color: int(number) for number, color in re.findall(r'(\d+) (\w+ \w+)', rule)}
|
||||
return rules
|
||||
|
||||
|
||||
def build_reverse_rules(rules):
|
||||
reverse_rules = defaultdict(list)
|
||||
for bag, inner_rules in rules.items():
|
||||
for c in inner_rules:
|
||||
reverse_rules[c].append(bag)
|
||||
return reverse_rules
|
||||
|
||||
|
||||
def part1(reverse_rules):
|
||||
queue = deque(("shiny gold",))
|
||||
may_contain_shiny_gold = set()
|
||||
while queue:
|
||||
color = queue.pop()
|
||||
for c in reverse_rules.get(color, []):
|
||||
if c not in may_contain_shiny_gold:
|
||||
may_contain_shiny_gold.add(c)
|
||||
queue.appendleft(c)
|
||||
return len(may_contain_shiny_gold)
|
||||
|
||||
|
||||
def part2(rules, color):
|
||||
return sum(number + number * part2(rules, c) for c, number in rules[color].items())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
55
adventofcode/2020/day8/day8.py
Normal file
55
adventofcode/2020/day8/day8.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
|
||||
def part1(instructions):
|
||||
instruction_pointer = 0
|
||||
accumulator = 0
|
||||
visited_instructions = set()
|
||||
while instruction_pointer not in visited_instructions: # return before executing any instruction a second time
|
||||
if instruction_pointer >= len(instructions): # stop the program when ip is out of bounds
|
||||
break
|
||||
visited_instructions.add(instruction_pointer)
|
||||
instruction, argument = instructions[instruction_pointer].split(" ")
|
||||
if instruction == "acc":
|
||||
accumulator += int(argument)
|
||||
instruction_pointer += 1
|
||||
elif instruction == "jmp":
|
||||
value = int(argument)
|
||||
instruction_pointer += value
|
||||
else:
|
||||
instruction_pointer += 1
|
||||
return instruction_pointer, accumulator
|
||||
|
||||
|
||||
def part2(instructions):
|
||||
for index, line in enumerate(instructions):
|
||||
permutation = generate_permutation(instructions, line, index)
|
||||
if permutation is None:
|
||||
continue
|
||||
instruction_pointer, accumulator = part1(permutation)
|
||||
if instruction_pointer == len(permutation):
|
||||
return accumulator
|
||||
|
||||
|
||||
def generate_permutation(instructions, line, index):
|
||||
permutation = instructions[:]
|
||||
instruction, arg = line.split(" ")
|
||||
if instruction == "acc": # don't replace acc operations
|
||||
return
|
||||
elif instruction == "nop":
|
||||
permutation[index] = f"jmp {arg}"
|
||||
elif instruction == "jmp":
|
||||
permutation[index] = f"nop {arg}"
|
||||
return permutation
|
||||
|
||||
|
||||
def main(inp):
|
||||
instructions = [line.rstrip() for line in fileinput.input()]
|
||||
print("Part 1 : (ip, acc) ", part1(instructions)[1])
|
||||
print("Part 2 : (ip, acc) ", part2(instructions))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
||||
|
54
adventofcode/2020/day9/day9.py
Normal file
54
adventofcode/2020/day9/day9.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
import itertools
|
||||
|
||||
|
||||
def part1(lines):
|
||||
preamble_size = 25
|
||||
for nums in window(lines, preamble_size + 1):
|
||||
candidate = nums[-1]
|
||||
if not test_number(candidate, nums[:-1]):
|
||||
return candidate
|
||||
return -1
|
||||
|
||||
|
||||
def window(seq, n):
|
||||
it = iter(seq)
|
||||
result = tuple(itertools.islice(it, n))
|
||||
if len(result) == n:
|
||||
yield result
|
||||
for elem in it:
|
||||
result = result[1:] + (elem,)
|
||||
yield result
|
||||
|
||||
|
||||
def test_number(num, previous):
|
||||
sums = set(sum(x) for x in itertools.combinations(previous, 2))
|
||||
return num in sums
|
||||
|
||||
|
||||
def part2(lines, target: int):
|
||||
total = 0
|
||||
visited = []
|
||||
for index, _ in enumerate(lines):
|
||||
i = index
|
||||
while total < target:
|
||||
total += lines[i]
|
||||
visited.append(lines[i])
|
||||
i += 1
|
||||
if total == target:
|
||||
return max(visited) + min(visited)
|
||||
visited.clear()
|
||||
total = 0
|
||||
|
||||
|
||||
def main(f):
|
||||
lines = [int(l.rstrip()) for l in f]
|
||||
invalid_number = part1(lines)
|
||||
print("part1 ", invalid_number)
|
||||
print("part2 ", part2(lines, invalid_number))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import fileinput
|
||||
main(fileinput.input())
|
||||
|
Reference in New Issue
Block a user