Go to the site, Download Python script or Fork me on GitHub.
1721
979
366
299
675
1456
def problem_1_a(lines):
'''514579'''
import itertools
numbers = [int(line) for line in lines]
for l, r in itertools.combinations(numbers, 2):
if l + r == 2020:
return l * r
def problem_1_b(lines):
'''241861950'''
import itertools
numbers = [int(line) for line in lines]
for a, b, c in itertools.combinations(numbers, 3):
if a + b + c == 2020:
return a * b * c
1-3 a: abcde
1-3 b: cdefg
2-9 c: ccccccccc
def problem_2_a(lines):
'''2'''
import re
def is_valid(line):
min_, max_, letter, password = re.match('^(\d+)-(\d+) (\w): (\w+)$', line).groups()
return int(min_) <= password.count(letter) <= int(max_)
return sum(is_valid(line) for line in lines)
def problem_2_b(lines):
'''1'''
import re
def is_valid(line):
i_1, i_2, letter, password = re.match('^(\d+)-(\d+) (\w): (\w+)$', line).groups()
return (password[int(i_1)-1] == letter) + (password[int(i_2)-1] == letter) == 1
return sum(is_valid(line) for line in lines)
..##.......
#...#...#..
.#....#..#.
..#.#...#.#
.#...##..#.
..#.##.....
.#.#.#....#
.#........#
#.##...#...
#...##....#
.#..#...#.#
Starting at the top-left corner of your map and following a slope of right 3 and down 1, how many trees would you encounter?
def problem_3_a(lines):
'''7'''
import collections
P = collections.namedtuple('P', 'x y')
positions = (P(x=y*3, y=y) for y in range(len(lines)))
is_tree = lambda p: lines[p.y][p.x % len(lines[0])] == '#'
return sum(is_tree(p) for p in positions)
What do you get if you multiply together the number of trees encountered on each of the listed slopes?
def problem_3_b(lines):
'''336'''
import collections, functools, itertools, operator as op
P = collections.namedtuple('P', 'x y')
def get_positions(slope):
x_generator = itertools.count(start=0, step=slope.x)
return (P(next(x_generator), y) for y in range(0, len(lines), slope.y))
is_tree = lambda p: lines[p.y][p.x % len(lines[0])] == '#'
count_trees = lambda slope: sum(is_tree(p) for p in get_positions(slope))
slopes = [P(x=1, y=1), P(x=3, y=1), P(x=5, y=1), P(x=7, y=1), P(x=1, y=2)]
return functools.reduce(op.mul, (count_trees(slope) for slope in slopes))
ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm
iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929
hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm
hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in
def problem_4_a(lines):
'''2'''
passports = ' '.join(lines).split(' ')
get_keys = lambda passport: {item.split(':')[0] for item in passport.split()}
is_valid = lambda passport: len(get_keys(passport) - {'cid'}) == 7
return sum(is_valid(p) for p in passports)
def problem_4_b(lines):
'''2'''
import re
def is_passport_valid(passport):
return sum(is_field_valid(*item.split(':')) for item in passport.split()) == 7
def is_field_valid(key, value):
RULES = dict(
byr=lambda v: 1920 <= int(v) <= 2002,
iyr=lambda v: 2010 <= int(v) <= 2020,
eyr=lambda v: 2020 <= int(v) <= 2030,
hgt=lambda v: 150 <= int(v[:-2]) <= 193 if 'cm' in v else 59 <= int(v[:-2]) <= 76,
hcl=lambda v: re.match('#[0-9a-f]{6}$', v) != None,
ecl=lambda v: v in 'amb blu brn gry grn hzl oth'.split(),
pid=lambda v: re.match('\d{9}$', v) != None
)
try:
return RULES[key](value)
except Exception:
return False
passports = ' '.join(lines).split(' ')
return sum(is_passport_valid(p) for p in passports)
BBFFBBFLLR
BBFFBBFLRL
BBFFBBFRLL
def problem_5_a(lines):
'''820'''
get_bin = lambda code: ''.join('0' if ch in 'FL' else '1' for ch in code)
get_id = lambda code: int(get_bin(code), 2)
return max(get_id(code) for code in lines)
def problem_5_b(lines):
'''819'''
get_bin = lambda code: ''.join('0' if ch in 'FL' else '1' for ch in code)
get_id = lambda code: int(get_bin(code), 2)
taken_ids = {get_id(code) for code in lines}
all_ids = range(min(taken_ids), max(taken_ids)+1)
return (set(all_ids) - taken_ids).pop()
abc
a
b
c
ab
ac
a
a
a
a
b
For each group, count the number of questions to which anyone answered "yes". What is the sum of those counts?
def problem_6_a(lines):
'''11'''
groups = (set(group) - {' '} for group in ' '.join(lines).split(' '))
return sum(len(group) for group in groups)
For each group, count the number of questions to which everyone answered "yes". What is the sum of those counts?
def problem_6_b(lines):
'''6'''
import functools, operator as op
groups = ' '.join(lines).split(' ')
split_group = lambda group: (set(a) for a in group.split(' '))
get_common_answers = lambda group: functools.reduce(op.and_, split_group(group))
return sum(len(get_common_answers(group)) for group in groups)
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.
def problem_7_a(lines):
'''4'''
import re
def parse_line(line):
container, contents = line.split(' bags contain ')
if contents == 'no other bags.':
return container, set()
get_color = lambda token: re.search('\d+ (.*) .*$', token).group(1)
return container, {get_color(a) for a in contents.split(',')}
bags = dict(parse_line(line) for line in lines)
out = {k for k, v in bags.items() if 'shiny gold' in v}
while True:
new_colors = {k for k, v in bags.items() if out & v}
if new_colors <= out:
return len(out)
out |= new_colors
def problem_7_b(lines):
'''32'''
import re
def parse_line(line):
container, contents = line.split(' bags contain ')
if contents == 'no other bags.':
return container, set()
get_number_and_color = lambda token: re.search('(\d+) (.*) .*$', token).groups()
return container, {get_number_and_color(a) for a in contents.split(',')}
def get_n_bags(color):
contents = bags[color]
if not contents:
return 1
return 1 + sum(int(n) * get_n_bags(color) for n, color in contents)
bags = dict(parse_line(line) for line in lines)
return get_n_bags('shiny gold') - 1
nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6
Run your copy of the boot code. Immediately before any instruction is executed a second time, what value is in the accumulator?
def problem_8_a(lines):
'''5'''
OPERATIONS = dict(
acc=lambda pc, accumulator, argument: (pc+1, accumulator+int(argument)),
jmp=lambda pc, accumulator, argument: (pc+int(argument), accumulator),
nop=lambda pc, accumulator, argument: (pc+1, accumulator)
)
pc = 0
accumulator = 0
executed = set()
while pc not in executed:
executed.add(pc)
operation, argument = lines[pc].split()
pc, accumulator = OPERATIONS[operation](pc, accumulator, argument)
return accumulator
Fix the program so that it terminates normally by changing exactly one jmp (to nop) or nop (to jmp). What is the value of the accumulator after the program terminates?
def problem_8_b(lines):
'''8'''
def main():
for program in program_generator():
result = run(program)
if result is not None:
return result
def program_generator():
for i, line in enumerate(lines):
if line.startswith('acc'):
continue
line = line.replace('jmp', 'nop') if 'jmp' in line else line.replace('nop', 'jmp')
yield lines[:i] + [line] + lines[i+1:]
def run(program):
OPERATIONS = dict(
acc=lambda pc, accumulator, argument: (pc+1, accumulator+int(argument)),
jmp=lambda pc, accumulator, argument: (pc+int(argument), accumulator),
nop=lambda pc, accumulator, argument: (pc+1, accumulator)
)
pc = 0
accumulator = 0
executed = set()
while pc not in executed:
executed.add(pc)
operation, argument = program[pc].split()
pc, accumulator = OPERATIONS[operation](pc, accumulator, argument)
if pc == len(program):
return accumulator
return None
return main()
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
21
22
23
24
25
45
65
The first step of attacking the weakness in the XMAS data is to find the first number in the list (after the preamble) which is not the sum of two of the 25 numbers before it. What is the first number that does not have this property?
def problem_9_a(lines):
'''65'''
import itertools
def is_sum(candidate, numbers):
return any(a + b == candidate for a, b in itertools.combinations(numbers, 2))
numbers = [int(line) for line in lines]
for i in range(25, len(numbers)):
if not is_sum(numbers[i], numbers[i-25:i]):
return numbers[i]
def problem_9_b(lines):
'''21'''
invalid_number = problem_9_a(lines)
numbers = [int(line) for line in lines]
for i in range(len(numbers)):
for j in range(i+2, len(numbers)):
sum_ = sum(numbers[i:j])
if sum_ == invalid_number:
return min(numbers[i:j]) + max(numbers[i:j])
elif sum_ > invalid_number:
break
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 problem_10_a(lines):
'''220'''
numbers = [0] + sorted(int(a) for a in lines)
deltas = [b-a for a, b in zip(numbers, numbers[1:])]
return deltas.count(1) * (deltas.count(3)+1)
What is the total number of distinct ways you can arrange the adapters to connect the charging outlet to your device?
def problem_10_b(lines):
'''19208'''
import functools, operator as op
numbers = sorted(int(a) for a in lines)
numbers = [0] + numbers + [numbers[-1]+3]
deltas = [b-a for a, b in zip(numbers, numbers[1:])]
d = ''.join(str(a) for a in deltas)
dd = [[1, 2, 4, 7, 13, 23][len(a)-1] for a in d.split('3') if a]
return functools.reduce(op.mul, dd)
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
Simulate your seating area by applying the seating rules repeatedly until no seats change state. How many seats end up occupied?
def problem_11_a(lines):
'''37'''
import collections
P = collections.namedtuple('P', 'x y')
def main():
layout = {P(x, y): ch for y, line in enumerate(lines) for x, ch in enumerate(line)}
while True:
new_layout = step(layout)
if new_layout == layout:
return list(layout.values()).count('#')
layout = new_layout
def step(layout):
out = dict(layout)
for p, ch in layout.items():
adjecent_chars = [layout.get(a) for a in get_adjecent_positions(p)]
if ch == 'L' and '#' not in adjecent_chars:
out[p] = '#'
elif ch == '#' and adjecent_chars.count('#') >= 4:
out[p] = 'L'
return out
def get_adjecent_positions(p):
DELTAS = [P(-1, -1), P(0, -1), P(1, -1), P(-1, 0), P(1, 0), P(-1, 1), P(0, 1), P(1, 1)]
return (P(p.x + dx, p.y + dy) for dx, dy in DELTAS)
return main()
Given the new visibility method and the rule change for occupied seats becoming empty, once equilibrium is reached, how many seats end up occupied?
def problem_11_b(lines):
'''26'''
import collections
P = collections.namedtuple('P', 'x y')
layout = {P(x, y): ch for y, line in enumerate(lines) for x, ch in enumerate(line)}
def main():
nonlocal layout
while True:
new_layout = {p: get_new_ch(p, ch) for p, ch in layout.items()}
if new_layout == layout:
return list(layout.values()).count('#')
layout = new_layout
def get_new_ch(p, ch):
DIRECTIONS = [P(-1, -1), P(0, -1), P(1, -1), P(-1, 0), P(1, 0), P(-1, 1), P(0, 1),
P(1, 1)]
visible_chairs = [get_visible_chair(p, direction) for direction in DIRECTIONS]
if ch == 'L' and '#' not in visible_chairs:
return '#'
elif ch == '#' and visible_chairs.count('#') >= 5:
return 'L'
return ch
def get_visible_chair(p, direction):
while p in layout:
p = P(p.x + direction.x, p.y + direction.y)
if p in layout and layout[p] in 'L#':
return layout[p]
return main()
F10
N3
F7
R90
F11
Figure out where the navigation instructions lead. What is the Manhattan distance between that location and the ship's starting position?
def problem_12_a(lines):
'''25'''
import collections, enum
P = collections.namedtuple('P', 'x y')
D = enum.Enum('D', 'n e s w')
def main():
p, d = P(0, 0), D.e
for line in lines:
p, d = step(p, d, line)
return abs(p.x) + abs(p.y)
def step(p, d, line):
DELTAS = {D.n: P(0, 1), D.e: P(1, 0), D.s: P(0, -1), D.w: P(-1, 0)}
ACTIONS = dict(
N=lambda p, d, arg: (P(p.x, p.y+arg), d),
S=lambda p, d, arg: (P(p.x, p.y-arg), d),
E=lambda p, d, arg: (P(p.x+arg, p.y), d),
W=lambda p, d, arg: (P(p.x-arg, p.y), d),
L=lambda p, d, arg: (p, turn(d, -arg)),
R=lambda p, d, arg: (p, turn(d, arg)),
F=lambda p, d, arg: (P(p.x + DELTAS[d].x*arg, p.y + DELTAS[d].y*arg), d)
)
action_id, arg = line[0], int(line[1:])
return ACTIONS[action_id](p, d, arg)
def turn(d, degrees):
directions = list(D)
index = (directions.index(d) + degrees//90) % len(directions)
return directions[index]
return main()
Figure out where the navigation instructions actually lead. What is the Manhattan distance between that location and the ship's starting position?
def problem_12_b(lines):
'''286'''
import collections
P = collections.namedtuple('P', 'x y')
def main():
p, waypoint = P(0, 0), P(10, 1)
for line in lines:
p, waypoint = step(p, waypoint, line)
return abs(p.x) + abs(p.y)
def step(p, waypoint, line):
ACTIONS = dict(
N=lambda p, waypoint, arg: (p, P(waypoint.x, waypoint.y+arg)),
S=lambda p, waypoint, arg: (p, P(waypoint.x, waypoint.y-arg)),
E=lambda p, waypoint, arg: (p, P(waypoint.x+arg, waypoint.y)),
W=lambda p, waypoint, arg: (p, P(waypoint.x-arg, waypoint.y)),
L=lambda p, waypoint, arg: (p, turn(waypoint, -arg)),
R=lambda p, waypoint, arg: (p, turn(waypoint, arg)),
F=lambda p, waypoint, arg: (P(p.x + waypoint.x*arg, p.y + waypoint.y*arg),
waypoint)
)
action_id, arg = line[0], int(line[1:])
return ACTIONS[action_id](p, waypoint, arg)
def turn(waypoint, degrees):
degrees += 360 if degrees < 0 else 0
TURN = {90: P(x=waypoint.y, y=-waypoint.x),
180: P(x=-waypoint.x, y=-waypoint.y),
270: P(x=-waypoint.y, y=waypoint.x)}
return TURN[degrees]
return main()
939
7,13,x,x,59,x,31,19
What is the ID of the earliest bus you can take to the airport multiplied by the number of minutes you'll need to wait for that bus?
def problem_13_a(lines):
'''295'''
import itertools
stamp = int(lines[0])
ids = [int(a) for a in lines[1].split(',') if a != 'x']
get_departures = lambda stamp, id_: range(0, stamp+id_, id_)
timetable = [get_departures(stamp, id_) for id_ in ids]
departure = min(a for a in itertools.chain.from_iterable(timetable) if a >= stamp)
id_ = [a for a in ids if departure % a == 0][0]
return id_ * (departure - stamp)
What is the earliest timestamp such that all of the listed bus IDs depart at offsets matching their positions in the list?
def problem_13_b(lines):
'''1068781'''
buses = [(offset, int(id_)) for offset, id_ in enumerate(lines[1].split(','))
if id_ != 'x']
stamp = 0
least_common_multiple = 1
for offset, id_ in buses:
while (stamp + offset) % id_ != 0:
stamp += least_common_multiple
least_common_multiple *= id_
return stamp
mask = 000000000000000000000000000000X1001X
mem[42] = 100
mask = 00000000000000000000000000000000X0XX
mem[26] = 1
Execute the initialization program. What is the sum of all values left in memory after it completes?
def problem_14_a(lines):
'''51'''
import re
def get_word(val, mask):
bin_val = bin(int(val))[2:]
bin_val_padded = '0' * (len(mask)-len(bin_val)) + bin_val
return ''.join(a if m == 'X' else m for a, m in zip(bin_val_padded, mask))
mem = {}
for line in lines:
if line.startswith('mask'):
_, mask = line.split(' = ')
continue
addr, val = re.search('\[(\d+)\] = (\d+)', line).groups()
mem[addr] = get_word(val, mask)
return sum(int(a, 2) for a in mem.values())
Execute the initialization program using an emulator for a version 2 decoder chip. What is the sum of all values left in memory after it completes?
def problem_14_b(lines):
'''208'''
import itertools, re
def address_generator(addr, mask):
bin_addr = bin(int(addr))[2:]
bin_addr_padded = '0' * (len(mask)-len(bin_addr)) + bin_addr
addr_template = ''.join(a if m == '0' else m for a, m in zip(bin_addr_padded, mask))
for floating_bits in itertools.product('01', repeat=addr_template.count('X')):
floating_bits = iter(floating_bits)
yield ''.join(next(floating_bits) if ch == 'X' else ch for ch in addr_template)
mem = {}
for line in lines:
if line.startswith('mask'):
_, mask = line.split(' = ')
continue
addr, val = re.search('\[(\d+)\] = (\d+)', line).groups()
for address in address_generator(addr, mask):
mem[address] = val
return sum(int(a) for a in mem.values())
0,3,6
def problem_15_a(lines):
'''436'''
*record, last_spoken = [int(a) for a in lines[0].split(',')]
for _ in range(len(record)+1, 2020):
delta = list(reversed(record)).index(last_spoken) + 1 if last_spoken in record else 0
record.append(last_spoken)
last_spoken = delta
return last_spoken
def problem_15_b(lines):
'''175594'''
*record, last_spoken = [int(a) for a in lines[0].split(',')]
record = {a: record.index(a)+1 for a in record}
for i in range(len(record)+1, 30000000):
delta = i - record[last_spoken] if last_spoken in record else 0
record[last_spoken] = i
last_spoken = delta
return last_spoken
class: 1-3 or 5-7
row: 6-11 or 33-44
departure: 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 problem_16_a(lines):
'''71'''
def get_valid_ranges(field):
for range_ in field.split(': ')[1].split(' or '):
start, stop = [int(a) for a in range_.split('-')]
yield range(start, stop+1)
fields, _, nerby_tickets = [a.split('\r') for a in '\r'.join(lines).split('\r\r')]
valid_ranges = [range_ for f in fields for range_ in get_valid_ranges(f)]
is_valid = lambda value: any(value in a for a in valid_ranges)
values = [int(a) for a in ','.join(nerby_tickets[1:]).split(',')]
return sum(a for a in values if not is_valid(a))
Once you work out which field is which, look for the six fields on your ticket that start with the word departure. What do you get if you multiply those six values together?
def problem_16_b(lines):
'''14'''
import functools, operator as op, re
def main():
fields, your_ticket, nerby_tickets = \
[a.split('\r') for a in '\r'.join(lines).split('\r\r')]
field_values = get_field_values(fields)
tickets = get_valid_tickets(field_values, your_ticket[1:] + nerby_tickets[1:])
out = {i: set(field_values.keys()) for i in range(len(tickets[0]))}
purge_solutions(out, field_values, tickets)
while any(len(fields) > 1 for fields in out.values()):
remove_solved_fields(out)
indices = [i for i, fields in out.items() if 'departure' in next(iter(fields))]
return functools.reduce(op.mul, (tickets[0][i] for i in indices))
def get_field_values(fields):
def get_item(field):
name, a, b, c, d = re.match('^(.*): (\d+)-(\d+) or (\d+)-(\d+)', field).groups()
return name, set(range(int(a), int(b)+1)) | set(range(int(c), int(d)+1))
return dict(get_item(field) for field in fields)
def get_valid_tickets(field_values, tickets):
valid_values = functools.reduce(op.or_, field_values.values())
tickets = [[int(a) for a in t.split(',')] for t in tickets]
return tuple(t for t in tickets if set(t) <= valid_values)
def purge_solutions(out, field_values, tickets):
get_column = lambda i: [a[i] for a in tickets]
for col_index in range(len(tickets[0])):
for val in get_column(col_index):
for field_name, values in field_values.items():
if val not in values:
out[col_index] -= {field_name}
def remove_solved_fields(out):
solved_fields = {next(iter(fields)) for fields in out.values() if len(fields) == 1}
for possible_fields in out.values():
possible_fields -= solved_fields if len(possible_fields) > 1 else set()
return main()
.#.
..#
###
Starting with your given initial configuration, simulate six cycles. How many cubes are left in the active state after the sixth cycle?
def problem_17_a(lines):
'''112'''
import collections, itertools
P = collections.namedtuple('P', 'x y z')
def get_neighbours(p):
DELTAS = set(itertools.product([-1, 0, 1], repeat=3)) - {(0, 0, 0)}
return {P(p.x + dx, p.y + dy, p.z + dz) for dx, dy, dz in DELTAS}
def should_be_active(p):
n_active_neighbours = len(get_neighbours(p) & cubes)
return (p in cubes and 2 <= n_active_neighbours <= 3) or \
(p not in cubes and n_active_neighbours == 3)
cubes = {P(x, y, 0) for y, line in enumerate(lines)
for x, ch in enumerate(line) if ch == '#'}
for _ in range(6):
candidates = {p for cube in cubes for p in get_neighbours(cube)}
cubes = {p for p in candidates if should_be_active(p)}
return len(cubes)
Starting with your given initial configuration, simulate six cycles in a 4-dimensional space. How many cubes are left in the active state after the sixth cycle?
def problem_17_b(lines):
'''848'''
import collections, itertools
P = collections.namedtuple('P', 'x y z w')
def get_neighbours(p):
DELTAS = set(itertools.product([-1, 0, 1], repeat=4)) - {(0, 0, 0, 0)}
return {P(p.x+dx, p.y+dy, p.z+dz, p.w+dw) for dx, dy, dz, dw in DELTAS}
def should_be_active(p):
n_active_neighbours = len(get_neighbours(p) & cubes)
return (p in cubes and 2 <= n_active_neighbours <= 3) or \
(p not in cubes and n_active_neighbours == 3)
cubes = {P(x, y, 0, 0) for y, line in enumerate(lines)
for x, ch in enumerate(line) if ch == '#'}
for _ in range(6):
candidates = {p for cube in cubes for p in get_neighbours(cube)}
cubes = {p for p in candidates if should_be_active(p)}
return len(cubes)
5 + (8 * 3 + 9 + 3 * 4 * 3)
Before you can help with the homework, you need to understand it yourself. Evaluate the expression on each line of the homework; what is the sum of the resulting values?
def problem_18_a(lines):
'''437'''
import operator as op, re
def calculate_line(line):
while '(' in line:
line = re.sub('\(([^()]*?)\)', lambda m: calculate(m.group(1)), line)
return int(calculate(line))
def calculate(s):
out, op_ = 0, op.add
for el in s.split():
if el.isdigit():
out = op_(out, int(el))
else:
op_ = op.add if el == '+' else op.mul
return str(out)
return sum(calculate_line(line) for line in lines)
What do you get if you add up the results of evaluating the homework problems using these new rules?
def problem_18_b(lines):
'''1445'''
import operator as op, re
def calculate(s):
while '(' in s:
s = re.sub('\(([^()]*?)\)', lambda m: calculate(m.group(1)), s)
while '+' in s:
s = re.sub('(\d+) \+ (\d+)', lambda m: str(int(m.group(1)) + int(m.group(2))), s)
while '*' in s:
s = re.sub('(\d+) \* (\d+)', lambda m: str(int(m.group(1)) * int(m.group(2))), s)
return s
return sum(int(calculate(line)) for line in lines)
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 problem_19_a(lines):
'''3'''
def parse_rule(line):
id_, value = line.split(': ')
if '"' in value:
return id_, value.strip('"')
return id_, [a.split() for a in value.split('|')]
def is_valid(message, so_far, rule_id):
subrules = rules[rule_id]
if type(subrules) == str:
return so_far + subrules if message.startswith(so_far + subrules) else False
elif len(subrules) == 1:
tmp = so_far
for subrule in subrules[0]:
tmp = is_valid(message, tmp, subrule)
if tmp == False:
return False
return message == tmp if rule_id == '0' else tmp
else:
for s in subrules:
tmp = so_far
for subrule in s:
tmp = is_valid(message, tmp, subrule)
if tmp == False:
break
if tmp:
return tmp
return False
rule_lines, messages = [a.split('\r') for a in '\r'.join(lines).split('\r\r')]
rules = dict(parse_rule(l) for l in rule_lines)
return sum(is_valid(m, '', '0') for m in messages)
def problem_19_b(lines):
'''12'''
def parse_rule(line):
id_, value = line.split(': ')
if '"' in value:
return int(id_), value
return int(id_), [[int(a) for a in v.split()] for v in value.split('|')]
def is_valid(message, seq):
if message == '' or seq == []:
return message == '' and seq == []
rule = rules[seq[0]]
if '"' in rule:
return is_valid(message[1:], seq[1:]) if message[0] in rule else False
else:
return any(is_valid(message, r + seq[1:]) for r in rule)
rule_lines, messages = [a.split('\r') for a in '\r'.join(lines).split('\r\r')]
rule_lines += ['8: 42 | 42 8', '11: 42 31 | 42 11 31']
rules = dict(parse_rule(l) for l in rule_lines)
return sum(is_valid(m, [0]) for m in messages)
Tile 2311:
..##.#..#.
##..#.....
#...##..#.
####.#...#
##.##.###.
##...#.###
.#.#.#..##
..#....#..
###...#.#.
..###..###
Tile 1951:
#.##...##.
#.####...#
.....#..##
#...######
.##.#....#
.###.#####
###.##.##.
.###....#.
..#.#..#.#
#...##.#..
Tile 1171:
####...##.
#..##.#..#
##.#..#.#.
.###.####.
..###.####
.##....##.
.#...####.
#.##.####.
####..#...
.....##...
Tile 1427:
###.##.#..
.#..#.##..
.#.##.#..#
#.#.#.##.#
....#...##
...##..##.
...#.#####
.#.####.#.
..#..###.#
..##.#..#.
Tile 1489:
##.#.#....
..##...#..
.##..##...
..#...#...
#####...#.
#..#.#.#.#
...#.#.#..
##.#...##.
..##.##.##
###.##.#..
Tile 2473:
#....####.
#..#.##...
#.##..#...
######.#.#
.#...#.#.#
.#########
.###.#..#.
########.#
##...##.#.
..###.#.#.
Tile 2971:
..#.#....#
#...###...
#.#.###...
##.##..#..
.#####..##
.#..####.#
#..#.#..#.
..####.###
..#.#.###.
...#.#.#.#
Tile 2729:
...#.#.#.#
####.#....
..#.#.....
....#..#.#
.##..##.#.
.#.####...
####.#.#..
##.####...
##..#.##..
#.##...##.
Tile 3079:
#.#.#####.
.#..######
..#.......
######....
####.#..#.
.#...#.##.
#.#####.##
..#.###...
..#.......
..#.###...
Assemble the tiles into an image. What do you get if you multiply together the IDs of the four corner tiles?
def problem_20_a(lines):
'''20899048083289'''
import collections
def get_tile(tile_lines):
Tile = collections.namedtuple('Tile', 'id sides')
id_, *rows = tile_lines
sides = [rows[0], ''.join(r[-1] for r in rows), rows[-1], ''.join(r[0] for r in rows)]
sides += [''.join(reversed(a)) for a in sides]
return Tile(int(id_[-5:-1]), sides)
def count_sides(tiles):
side_counter = collections.Counter()
for tile in tiles:
for side in tile.sides:
side_counter[side] += 1
return side_counter
tiles_lines = [a.split('\r') for a in '\r'.join(lines).split('\r\r')]
tiles = [get_tile(tile_lines) for tile_lines in tiles_lines]
side_counter = count_sides(tiles)
out = 1
for tile in tiles:
if sum(side_counter[side] == 1 for side in tile.sides) == 4:
out *= tile.id
return out
mxmxvkd kfcds sqjhc nhms (contains dairy, fish)
trh fvjkl sbzzf mxmxvkd (contains dairy)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd sbzzf (contains fish)
Determine which ingredients cannot possibly contain any of the allergens in your list. How many times do any of those ingredients appear?
def problem_21_a(lines):
'''5'''
import collections, functools, itertools, operator as op, re
def get_food(line):
ingreds, allergs = re.match('^(.*) \(contains (.*)\)$', line).groups()
return set(ingreds.split()), set(allergs.split(', '))
foods = tuple(get_food(line) for line in lines)
ingredient_counter = collections.Counter(i for ingreds, _ in foods for i in ingreds)
ingredients = functools.reduce(op.or_, (ingreds for ingreds, _ in foods))
allergens = functools.reduce(op.or_, (allergs for _, allergs in foods))
out = dict.fromkeys(ingredients, None)
allergen_cycle = itertools.cycle(allergens)
while any(allergs for _, allergs in foods):
allergen = next(allergen_cycle)
candidate_ingreds = [ingreds for ingreds, allergs in foods if allergen in allergs]
if not candidate_ingreds:
continue
intersection = functools.reduce(op.and_, candidate_ingreds)
if len(intersection) != 1:
continue
ingredient = intersection.pop()
out[ingredient] = allergen
for ingreds, allergs in foods:
ingreds.discard(ingredient)
allergs.discard(allergen)
ingredients_without_allergens = [ingred for ingred, allerg in out.items() if not allerg]
return sum(ingredient_counter[a] for a in ingredients_without_allergens)
def problem_21_b(lines):
'''mxmxvkd,sqjhc,fvjkl'''
import collections, functools, itertools, operator as op, re
def get_food(line):
ingreds, allergs = re.match('^(.*) \(contains (.*)\)$', line).groups()
return set(ingreds.split()), set(allergs.split(', '))
foods = tuple(get_food(line) for line in lines)
ingredient_counter = collections.Counter(i for ingreds, _ in foods for i in ingreds)
ingredients = functools.reduce(op.or_, (ingreds for ingreds, _ in foods))
allergens = functools.reduce(op.or_, (allergs for _, allergs in foods))
out = dict.fromkeys(ingredients, None)
allergen_cycle = itertools.cycle(allergens)
while any(allergs for _, allergs in foods):
allergen = next(allergen_cycle)
candidate_ingreds = [ingreds for ingreds, allergs in foods if allergen in allergs]
if not candidate_ingreds:
continue
intersection = functools.reduce(op.and_, candidate_ingreds)
if len(intersection) != 1:
continue
ingredient = intersection.pop()
out[ingredient] = allergen
for ingreds, allergs in foods:
ingreds.discard(ingredient)
allergs.discard(allergen)
ingredients_with_allergens = [(ingred, allerg) for ingred, allerg in out.items() if allerg]
return ','.join(a for a, _ in sorted(ingredients_with_allergens, key=op.itemgetter(1)))
Player 1:
9
2
6
3
1
Player 2:
5
8
4
7
10
Play the small crab in a game of Combat using the two decks you just dealt. What is the winning player's score?
def problem_22_a(lines):
'''306'''
import collections, itertools
get_deck = lambda cards: collections.deque(int(card) for card in cards)
deck_1, deck_2 = (get_deck(a.split('\r')[1:]) for a in '\r'.join(lines).split('\r\r'))
while deck_1 and deck_2:
card_1, card_2 = deck_1.popleft(), deck_2.popleft()
if card_1 > card_2:
deck_1.extend([card_1, card_2])
else:
deck_2.extend([card_2, card_1])
winning_deck = deck_1 if deck_1 else deck_2
return sum(a*b for a, b in zip(reversed(winning_deck), itertools.count(1)))