My Advent of Code solutions in Python. kevinyap.ca/2019/12/going-fast-in-advent-of-code/
advent-of-code python
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add files for 2020

+2058 -13
+17
2020/day01.py
··· 1 + import fileinput 2 + 3 + nums = [int(x) for x in fileinput.input()] 4 + 5 + part_1 = None 6 + part_2 = None 7 + 8 + for m in nums: 9 + for n in nums: 10 + for o in nums: 11 + if m + n == 2020: 12 + part_1 = m * n 13 + elif m + n + o == 2020: 14 + part_2 = m * n * o 15 + 16 + print "Part 1:", part_1 17 + print "Part 2:", part_2
+17
2020/day02.py
··· 1 + import fileinput 2 + from utils import parse_line 3 + 4 + part_1 = 0 5 + part_2 = 0 6 + 7 + for line in fileinput.input(): 8 + start, end, letter, pwd = parse_line(r'(\d+)-(\d+) (\w+): (\w+)', line) 9 + 10 + if start <= pwd.count(letter) <= end: 11 + part_1 += 1 12 + 13 + if (pwd[start-1] == letter) ^ (pwd[end-1] == letter): 14 + part_2 += 1 15 + 16 + print "Part 1:", part_1 17 + print "Part 2:", part_2
+30
2020/day03.py
··· 1 + import fileinput 2 + 3 + 4 + grid = [line.strip() for line in fileinput.input()] 5 + 6 + max_y = len(grid) 7 + max_x = len(grid[0]) 8 + 9 + part_1 = 0 10 + part_2 = 1 11 + 12 + for xs, ys in ((1, 1), (3, 1), (5, 1), (7, 1), (1, 2)): 13 + x = 0 14 + y = 0 15 + tot = 0 16 + 17 + while y < max_y: 18 + if grid[y][x] == '#': 19 + tot += 1 20 + 21 + x = (x + xs) % max_x 22 + y += ys 23 + 24 + part_2 *= tot 25 + 26 + if (xs, ys) == (3, 1): 27 + part_1 = tot 28 + 29 + print part_1 30 + print part_2
+50
2020/day04.py
··· 1 + import fileinput 2 + from collections import OrderedDict 3 + 4 + def validate_hgt(hgt): 5 + if hgt.endswith('cm'): 6 + return 150 <= int(hgt[:-2]) <= 193 7 + elif hgt.endswith('in'): 8 + return 59 <= int(hgt[:-2]) <= 76 9 + 10 + return False 11 + 12 + VALIDATION_FNS = { 13 + 'byr': lambda v: 1920 <= int(v) <= 2002, 14 + 'iyr': lambda v: 2010 <= int(v) <= 2020, 15 + 'eyr': lambda v: 2020 <= int(v) <= 2030, 16 + 'hgt': validate_hgt, 17 + 'hcl': lambda v: len(v) == 7 and v[0] == '#' and all(c in 'abcdef1234567890' for c in v[1:]), 18 + 'ecl': lambda v: v in ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth'], 19 + 'pid': lambda v: len(v) == 9 and all(c in '1234567890' for c in v), 20 + } 21 + 22 + # Parse problem input 23 + passports = [] 24 + port = OrderedDict() 25 + 26 + for line in fileinput.input(): 27 + line = line.strip() 28 + if not line: 29 + passports.append(port) 30 + port = OrderedDict() 31 + else: 32 + parts = [x.split(':') for x in line.split()] 33 + for a, b in parts: 34 + port[a] = b 35 + 36 + passports.append(port) 37 + 38 + part_1 = 0 39 + part_2 = 0 40 + 41 + for p in passports: 42 + if all(k in p for k in VALIDATION_FNS): 43 + part_1 += 1 44 + 45 + if all(VALIDATION_FNS.get(k, lambda x: True)(v) for k, v in p.items()): 46 + part_2 += 1 47 + 48 + print part_1 49 + print part_2 50 +
+24
2020/day05.py
··· 1 + import fileinput 2 + from collections import Counter 3 + 4 + 5 + seen = set() 6 + max_seat_id = 0 7 + min_row = 1000 8 + max_row = 0 9 + 10 + for line in fileinput.input(): 11 + row = int(line[:7].replace('F', '0').replace('B', '1'), 2) 12 + col = int(line[7:10].replace('L', '0').replace('R', '1'), 2) 13 + 14 + min_row = min(row, min_row) 15 + max_row = max(row, max_row) 16 + max_seat_id = max(row * 8 + col, max_seat_id) 17 + seen.add((row, col)) 18 + 19 + print "Highest seat ID:", max_seat_id 20 + 21 + for row in range(min_row + 1, max_row - 1): 22 + for col in range(8): 23 + if (row, col) not in seen: 24 + print "Your seat ID:", row * 8 + col
+22
2020/day06.py
··· 1 + import fileinput 2 + from string import ascii_lowercase 3 + 4 + data = ''.join([line for line in fileinput.input()]) 5 + groups = [g.split('\n') for g in data.split('\n\n')] 6 + 7 + part_1 = 0 8 + part_2 = 0 9 + 10 + for group in groups: 11 + anyone = set() 12 + everyone = set(ascii_lowercase) 13 + 14 + for person in group: 15 + anyone |= set(person) 16 + everyone &= set(person) 17 + 18 + part_1 += len(anyone) 19 + part_2 += len(everyone) 20 + 21 + print part_1 22 + print part_2
+49
2020/day07.py
··· 1 + import fileinput 2 + from collections import defaultdict 3 + from utils import parse_line 4 + 5 + GOAL = 'shiny gold' 6 + 7 + graph = defaultdict(set) 8 + 9 + for i, line in enumerate(fileinput.input()): 10 + line = line.strip() 11 + color, rest = parse_line(r'(\w+ \w+) bags contain (.+)+', line) 12 + rest = rest.replace('.', '').split(',') 13 + 14 + for r in rest: 15 + if r != 'no other bags': 16 + parts = r.split() 17 + new_color = ' '.join(parts[1:3]) 18 + graph[color].add((new_color, int(parts[0]))) 19 + 20 + 21 + def dfs_1(node): 22 + if node == GOAL: 23 + return True 24 + 25 + return any(dfs_1(n) for n, _ in graph[node]) 26 + 27 + 28 + def dfs_2(node, count=1): 29 + tot = 1 30 + 31 + for nxt, amt in graph[node]: 32 + tot += dfs_2(nxt, amt) 33 + 34 + return count * tot 35 + 36 + part_1 = 0 37 + 38 + keys = graph.keys() 39 + 40 + for bag in keys: 41 + print bag 42 + if dfs_1(bag): 43 + part_1 += 1 44 + print "good" 45 + 46 + print part_1 - 1 47 + 48 + 49 + print dfs_2(GOAL) - 1
+49
2020/day08.py
··· 1 + import fileinput 2 + from copy import deepcopy 3 + from utils import parse_nums 4 + 5 + TAPE = [] 6 + 7 + for line in fileinput.input(): 8 + ins = line.split(' ')[0] 9 + TAPE.append([ins, parse_nums(line)]) 10 + 11 + 12 + def emulate(tape, pc=0, acc=0): 13 + """Returns (acc, pc, loop_detected)""" 14 + seen = set() 15 + while pc < len(tape): 16 + if pc in seen: 17 + return (acc, pc, True) 18 + else: 19 + seen.add(pc) 20 + 21 + ins, ops = tape[pc] 22 + 23 + if ins == 'acc': 24 + acc += ops[0] 25 + pc += 1 26 + elif ins == 'jmp': 27 + pc += ops[0] 28 + else: 29 + pc += 1 30 + 31 + return (acc, pc, False) 32 + 33 + 34 + print "ACC at repeated instruction:", emulate(TAPE)[0] 35 + 36 + for i in range(len(TAPE)): 37 + tape = deepcopy(TAPE) 38 + 39 + if tape[i][0] == 'acc': 40 + continue 41 + else: 42 + if tape[i][0] == 'jmp': 43 + tape[i][0] = 'nop' 44 + else: # ins == 'nop' 45 + tape[i][0] = 'jmp' 46 + 47 + acc, pc, loop_detected = emulate(tape) 48 + if not loop_detected: 49 + print "ACC with modified termination:", acc
+22
2020/day09.py
··· 1 + import fileinput 2 + from itertools import permutations 3 + 4 + SEQ = [int(x) for x in fileinput.input()] 5 + LEN = 25 6 + 7 + for i in range(LEN, len(SEQ)): 8 + for x, y in permutations(SEQ[i-LEN:i], 2): 9 + if x + y == SEQ[i]: 10 + break 11 + else: 12 + INVALID = SEQ[i] 13 + print "Part 1:", INVALID 14 + break 15 + 16 + for n in range(2, len(SEQ)): 17 + tot = 0 18 + for i in range(len(SEQ)-n): 19 + tot = sum(SEQ[i:i+n]) 20 + if tot == INVALID: 21 + print "Part 2:", min(SEQ[i:i+n]) + max(SEQ[i:i+n]) 22 +
+39
2020/day10.py
··· 1 + import fileinput 2 + from collections import Counter 3 + from utils import memoize 4 + 5 + NUMS = [int(x) for x in fileinput.input()] 6 + 7 + NUMS.append(0) 8 + NUMS.append(max(NUMS) + 3) 9 + NUMS.sort() 10 + 11 + deltas = Counter() 12 + curr = 0 13 + 14 + for n in NUMS: 15 + deltas[n - curr] += 1 16 + curr = n 17 + 18 + print "Part 1:", deltas[1] * deltas[3] 19 + 20 + 21 + @memoize 22 + def dp(i): 23 + if i >= len(NUMS) - 1: 24 + return 1 25 + 26 + ways = 0 27 + 28 + if i + 1 < len(NUMS) and NUMS[i + 1] - NUMS[i] <= 3: 29 + ways += dp(i + 1) 30 + 31 + if i + 2 < len(NUMS) and NUMS[i + 2] - NUMS[i] <= 3: 32 + ways += dp(i + 2) 33 + 34 + if i + 3 < len(NUMS) and NUMS[i + 3] - NUMS[i] <= 3: 35 + ways += dp(i + 3) 36 + 37 + return ways 38 + 39 + print "Part 2:", dp(0)
+119
2020/day11.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from heapq import heappush, heappop, heapify 9 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 10 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 11 + 12 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 13 + from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 14 + from utils import new_table, transposed, rotated # NOQA 15 + from utils import md5, sha256, knot_hash # NOQA 16 + from utils import VOWELS, CONSONANTS # NOQA 17 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 18 + 19 + # Itertools Functions: 20 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 21 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 22 + # combinations('ABCD', 2) AB AC AD BC BD CD 23 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 24 + 25 + tot = 0 26 + res = [] 27 + table = new_table(None, width=2, height=4) 28 + graph = {} 29 + 30 + # Uncomment for multi-group style inputs. :c 31 + # data = ''.join([line for line in fileinput.input()]) 32 + # groups = [g.split('\n') for g in data.split('\n\n')] 33 + 34 + for i, line in enumerate(fileinput.input()): 35 + line = line.strip() 36 + nums = parse_nums(line) 37 + data = parse_line(r'', line) 38 + 39 + for j, c in enumerate(line): 40 + graph[Point(j, i)] = c 41 + 42 + res.append(line) 43 + 44 + if i == 0: 45 + print(data) 46 + 47 + max_x = len(res[0]) 48 + max_y = len(res) 49 + 50 + print graph 51 + print res 52 + 53 + state = graph 54 + 55 + while True: 56 + new_state = {} 57 + 58 + for y in range(max_y): 59 + for x in range(max_x): 60 + p = Point(x, y) 61 + occ = 0 62 + # for n in p.neighbours_8(): 63 + # if state.get(n, '.') == '#': 64 + # occ += 1 65 + 66 + for d in DIRS_8: 67 + np = p + d 68 + # np = p 69 + while 0 <= np.x < max_x and 0 <= np.y < max_y: 70 + if state.get(np, '.') == '.': 71 + np += d 72 + # break 73 + elif state.get(np, '.') == '#': 74 + occ += 1 75 + break 76 + else: 77 + break 78 + 79 + 80 + if state[p] == 'L' and occ == 0: 81 + new_state[p] = '#' 82 + 83 + elif state[p] == '#' and occ >= 5: 84 + new_state[p] = 'L' 85 + 86 + else: 87 + new_state[p] = state[p] 88 + 89 + 90 + 91 + a = '' 92 + b = '' 93 + for y in range(max_y): 94 + for x in range(max_x): 95 + p = Point(x, y) 96 + # print state[p], 97 + a += state[p] 98 + b += new_state[p] 99 + 100 + # print 101 + 102 + # print 103 + # print 104 + 105 + if a == b: 106 + tot = 0 107 + for y in range(max_y): 108 + for x in range(max_x): 109 + if new_state[Point(x, y)] == '#': 110 + tot += 1 111 + 112 + print tot 113 + 114 + break 115 + 116 + 117 + state = new_state 118 + 119 +
+35
2020/day12.py
··· 1 + import fileinput 2 + from utils import Point, DIRS 3 + 4 + DIR_MAP = 'NESW' 5 + 6 + p1 = Point(0, 0) 7 + p2 = Point(0, 0) 8 + wp = Point(10, 1) 9 + 10 + facing = 1 # start at 1 to match DIRS 11 + 12 + for line in fileinput.input(): 13 + d = line[0] 14 + n = int(line[1:]) 15 + 16 + if d in DIR_MAP: 17 + delta = DIRS[DIR_MAP.index(d)] * n 18 + p1 += delta 19 + wp += delta 20 + 21 + elif d == 'L' or d == 'R': 22 + turns = n // 90 23 + if d == 'L': 24 + turns = 4 - turns 25 + 26 + facing = (facing + turns) % 4 27 + 28 + wp = wp.rotate(turns) 29 + 30 + elif d == 'F': 31 + p1 += DIRS[facing] * n 32 + p2 += wp * n 33 + 34 + print "Part 1:", p1.manhattan 35 + print "Part 2:", p2.manhattan
+69
2020/day13.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 10 + 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 12 + from utils import chunks, gcd, lcm, crt, print_grid, min_max_xy # NOQA 13 + from utils import new_table, transposed, rotated # NOQA 14 + from utils import md5, sha256, knot_hash # NOQA 15 + from utils import VOWELS, CONSONANTS # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 17 + 18 + # Itertools Functions: 19 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 20 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 21 + # combinations('ABCD', 2) AB AC AD BC BD CD 22 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 23 + 24 + tot = 0 25 + res = [] 26 + board = {} 27 + table = new_table(None, width=2, height=4) 28 + 29 + timestamp = None 30 + buses = [] 31 + 32 + # Uncomment for multi-group style inputs. :c 33 + # data = ''.join([line for line in fileinput.input()]) 34 + # groups = [g.split('\n') for g in data.split('\n\n')] 35 + 36 + for y, line in enumerate(fileinput.input()): 37 + line = line.strip() 38 + nums = parse_nums(line) 39 + data = parse_line(r'', line) 40 + 41 + for x, c in enumerate(line): 42 + board[Point(x, y)] = c 43 + 44 + if y == 0: 45 + timestamp = int(line) 46 + else: 47 + for x in line.split(','): 48 + if x == 'x': 49 + buses.append(None) 50 + else: 51 + buses.append(int(x)) 52 + 53 + m = [] 54 + x = [] 55 + for i, b in enumerate(buses): 56 + if b: 57 + m.append(b) 58 + x.append(i) 59 + 60 + print crt(x, m) 61 + 62 + 63 + 64 + num = 1777307553937916 65 + l1 = 2265213528143033 66 + 67 + num2 = num - l1 68 + 69 + print abs(num2) < abs(num)
+83
2020/day14.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 10 + 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 12 + from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 13 + from utils import new_table, transposed, rotated # NOQA 14 + from utils import md5, sha256, knot_hash # NOQA 15 + from utils import VOWELS, CONSONANTS # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 17 + 18 + # Itertools Functions: 19 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 20 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 21 + # combinations('ABCD', 2) AB AC AD BC BD CD 22 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 23 + 24 + tot = 0 25 + res = [] 26 + board = {} 27 + table = new_table(None, width=2, height=4) 28 + 29 + # Uncomment for multi-group style inputs. :c 30 + # data = ''.join([line for line in fileinput.input()]) 31 + # groups = [g.split('\n') for g in data.split('\n\n')] 32 + 33 + for y, line in enumerate(fileinput.input()): 34 + line = line.strip() 35 + nums = parse_nums(line) 36 + data = parse_line(r'', line) 37 + 38 + a, b = line.split(' = ') 39 + # print parts 40 + 41 + if a == 'mask': 42 + res.append((None, b)) 43 + else: 44 + res.append((nums[0], nums[1])) 45 + 46 + mem = defaultdict() 47 + 48 + mask = None 49 + 50 + for a, b in res: 51 + if a is None: 52 + mask = b 53 + else: 54 + addr = a 55 + write = 0 56 + floats = [i for i, c in enumerate(reversed(mask)) if c == 'X'] 57 + # print floats 58 + for i, c in enumerate(reversed(mask)): 59 + if c == '1': 60 + write |= (1 << i) 61 + elif c == '0': 62 + write |= (addr & (1 << i)) 63 + 64 + writes = [write] 65 + print writes, floats 66 + for i in floats: 67 + new_writes = [] 68 + for w in writes: 69 + print w 70 + new_writes.append(w & ~(1 << i)) 71 + new_writes.append(w | (1 << i)) 72 + 73 + print new_writes 74 + writes = new_writes 75 + 76 + print writes 77 + 78 + 79 + for w in writes: 80 + mem[w] = b 81 + 82 + print sum(mem.values()) 83 +
+77
2020/day15.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 10 + 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 12 + from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 13 + from utils import new_table, transposed, rotated # NOQA 14 + from utils import md5, sha256, knot_hash # NOQA 15 + from utils import VOWELS, CONSONANTS # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 17 + 18 + # Itertools Functions: 19 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 20 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 21 + # combinations('ABCD', 2) AB AC AD BC BD CD 22 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 23 + 24 + tot = 0 25 + res = [] 26 + board = {} 27 + table = new_table(None, width=2, height=4) 28 + 29 + # Uncomment for multi-group style inputs. :c 30 + # data = ''.join([line for line in fileinput.input()]) 31 + # groups = [g.split('\n') for g in data.split('\n\n')] 32 + 33 + for y, line in enumerate(fileinput.input()): 34 + line = line.strip() 35 + nums = parse_nums(line) 36 + data = parse_line(r'', line) 37 + nums = [int(x) for x in line.split(',')] 38 + 39 + for x, c in enumerate(line): 40 + board[Point(x, y)] = c 41 + 42 + if y == 0: 43 + print(data) 44 + 45 + print nums 46 + 47 + 48 + 49 + seen = defaultdict(list) 50 + 51 + for i, n in enumerate(nums): 52 + seen[n].append(i) 53 + 54 + print seen 55 + 56 + spoken = nums[-1] 57 + i = len(nums) 58 + 59 + while True: 60 + # print spoken, i 61 + if len(seen[spoken]) == 1: 62 + new_spoke = 0 63 + else: 64 + new_spoke = i - seen[spoken][-2] - 1 65 + 66 + # print "newspoke", new_spoke 67 + 68 + spoken = new_spoke 69 + seen[spoken].append(i) 70 + 71 + i += 1 72 + if i == 30000000: 73 + break 74 + 75 + print spoken 76 + 77 +
+43
2020/day16.py
··· 1 + import fileinput 2 + from collections import defaultdict 3 + from utils import parse_nums, mul, transposed, resolve_mapping 4 + 5 + fields = {} 6 + tickets = [] 7 + 8 + for line in fileinput.input(): 9 + nums = parse_nums(line) 10 + if nums: 11 + if len(nums) == 4: 12 + fields[line.split(':')[0]] = [abs(x) for x in nums] 13 + else: 14 + tickets.append(nums) 15 + 16 + valid_tickets = [] 17 + part_1 = 0 18 + 19 + for ticket in tickets[1:]: 20 + for n in ticket: 21 + for a, b, c, d in fields.values(): 22 + if a <= n <= b or c <= n <= d: 23 + break 24 + 25 + else: 26 + part_1 += n 27 + break 28 + else: 29 + valid_tickets.append(ticket) 30 + 31 + print "Part 1:", part_1 32 + 33 + poss = defaultdict(set) 34 + 35 + for i, col in enumerate(transposed(valid_tickets)): 36 + for field, (a, b, c, d) in fields.items(): 37 + if all(a <= n <= b or c <= n <= d for n in col): 38 + poss[field].add(i) 39 + 40 + resolved = resolve_mapping(poss) 41 + 42 + departures = [resolved[field] for field in resolved if 'departure' in field] 43 + print "Part 2:", mul(tickets[0][i] for i in departures)
+52
2020/day17.py
··· 1 + import fileinput 2 + from collections import defaultdict 3 + from itertools import product 4 + 5 + grid_1 = defaultdict(lambda: '.') 6 + grid_2 = defaultdict(lambda: '.') 7 + 8 + for y, line in enumerate(fileinput.input()): 9 + for x, c in enumerate(line.strip()): 10 + grid_1[x, y, 0] = c 11 + grid_2[x, y, 0, 0] = c 12 + 13 + max_dim = max(x, y) 14 + 15 + 16 + for i in range(6): 17 + new_grid_1 = defaultdict(lambda: '.') 18 + new_grid_2 = defaultdict(lambda: '.') 19 + 20 + for x, y, z, w in product(range(-i - 1, max_dim + i + 1), repeat=4): 21 + neighs_1 = 0 22 + neighs_2 = 0 23 + 24 + for dx, dy, dz, dw in product(range(-1, 2), repeat=4): 25 + if dx == 0 and dy == 0 and dz == 0 and dw == 0: 26 + continue 27 + 28 + if grid_1[x + dx, y + dy, z + dz] == '#' and dw == 0: 29 + neighs_1 += 1 30 + if grid_2[x + dx, y + dy, z + dz, w + dw] == '#': 31 + neighs_2 += 1 32 + 33 + 34 + if grid_1[x, y, z] == '#' and (neighs_1 == 2 or neighs_1 == 3): 35 + new_grid_1[x, y, z] = '#' 36 + 37 + elif grid_1[x, y, z] == '.' and neighs_1 == 3: 38 + new_grid_1[x, y, z] = '#' 39 + 40 + if grid_2[x, y, z, w] == '#' and (neighs_2 == 2 or neighs_2 == 3): 41 + new_grid_2[x, y, z, w] = '#' 42 + 43 + elif grid_2[x, y, z, w] == '.' and neighs_2 == 3: 44 + new_grid_2[x, y, z, w] = '#' 45 + 46 + grid_1 = new_grid_1 47 + grid_2 = new_grid_2 48 + 49 + 50 + print "Part 1:", sum(c == '#' for c in grid_1.values()) 51 + print "Part 2:", sum(c == '#' for c in grid_2.values()) 52 +
+107
2020/day18.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 10 + 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 12 + from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 13 + from utils import new_table, transposed, rotated # NOQA 14 + from utils import md5, sha256, knot_hash # NOQA 15 + from utils import VOWELS, CONSONANTS # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 17 + 18 + # Itertools Functions: 19 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 20 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 21 + # combinations('ABCD', 2) AB AC AD BC BD CD 22 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 23 + 24 + tot = 0 25 + res = [] 26 + board = {} 27 + table = new_table(None, width=2, height=4) 28 + 29 + # Uncomment for multi-group style inputs. :c 30 + # data = ''.join([line for line in fileinput.input()]) 31 + # groups = [g.split('\n') for g in data.split('\n\n')] 32 + 33 + for y, line in enumerate(fileinput.input()): 34 + line = line.strip().replace(' ', '') 35 + nums = parse_nums(line) 36 + data = parse_line(r'', line) 37 + 38 + for x, c in enumerate(line): 39 + board[Point(x, y)] = c 40 + 41 + if y == 0: 42 + print(data) 43 + 44 + res.append(line) 45 + 46 + print tot 47 + 48 + NUMS = '0123456789' 49 + 50 + 51 + def is_number(str): 52 + try: 53 + int(str) 54 + return True 55 + except ValueError: 56 + return False 57 + 58 + def is_name(str): 59 + return re.match("\w+", str) 60 + 61 + def peek(stack): 62 + return stack[-1] if stack else None 63 + 64 + def apply_operator(operators, values): 65 + operator = operators.pop() 66 + right = values.pop() 67 + left = values.pop() 68 + values.append(eval("{0}{1}{2}".format(left, operator, right))) 69 + 70 + def greater_precedence(op1, op2): 71 + precedences = {'+' : 1, '-' : 0, '*' : 0, '/' : 0} 72 + return precedences[op1] > precedences[op2] 73 + 74 + def evaluate(expression): 75 + tokens = re.findall("[+/*()-]|\d+", expression) 76 + values = [] 77 + operators = [] 78 + for token in tokens: 79 + if is_number(token): 80 + values.append(int(token)) 81 + elif token == '(': 82 + operators.append(token) 83 + elif token == ')': 84 + top = peek(operators) 85 + while top is not None and top != '(': 86 + apply_operator(operators, values) 87 + top = peek(operators) 88 + operators.pop() # Discard the '(' 89 + else: 90 + # Operator 91 + top = peek(operators) 92 + while top is not None and top not in "()" and greater_precedence(top, token): 93 + apply_operator(operators, values) 94 + top = peek(operators) 95 + operators.append(token) 96 + while peek(operators) is not None: 97 + apply_operator(operators, values) 98 + 99 + return values[0] 100 + 101 + 102 + for line in res: 103 + a = evaluate(line) 104 + print a, '=', line 105 + tot += a 106 + 107 + print tot
+71
2020/day19.py
··· 1 + import fileinput 2 + from collections import defaultdict 3 + from utils import parse_nums, memoize 4 + 5 + @memoize 6 + def resolve(r): 7 + if type(rules[r]) == str: 8 + return [rules[r]] 9 + 10 + matches = [] 11 + for subrule in rules[r]: 12 + submatches = [''] 13 + for n in subrule: 14 + new = [] 15 + for m in resolve(n): 16 + for existing in submatches: 17 + new.append(existing + m) 18 + 19 + submatches = new 20 + 21 + matches.extend(submatches) 22 + 23 + return matches 24 + 25 + rules = defaultdict(list) 26 + messages = [] 27 + 28 + for line in fileinput.input(): 29 + line = line.strip() 30 + nums = parse_nums(line) 31 + 32 + if nums: 33 + parts = line.split(": ")[1].split(" | ") 34 + r = nums[0] 35 + for p in parts: 36 + if '"' in p: 37 + rules[r] = p[1] 38 + else: 39 + rules[r].append([int(x) for x in p.split(' ')]) 40 + 41 + elif line: 42 + messages.append(line) 43 + 44 + 45 + pl = len(resolve(42)[0]) 46 + 47 + part_1 = 0 48 + part_2 = 0 49 + 50 + for line in messages: 51 + if line in resolve(0): 52 + part_1 += 1 53 + 54 + orig_line = line 55 + a = 0 56 + b = 0 57 + 58 + while line[:pl] in resolve(42): 59 + line = line[pl:] 60 + a += 1 61 + 62 + while line[:pl] in resolve(31): 63 + line = line[pl:] 64 + b += 1 65 + 66 + if a > b and b > 0 and not line: 67 + print orig_line 68 + part_2 += 1 69 + 70 + print "Part 1:", part_1 71 + print "Part 2:", part_2
+329
2020/day20-solve.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 8 + 9 + from utils import parse_nums, mul, print_grid, transposed, rotated, Point, new_table 10 + 11 + tot = 0 12 + res = [] 13 + board = new_table('.', width=10, height=10) 14 + 15 + tiles = {} 16 + curr = 0 17 + sy = 0 18 + 19 + MONSTER = """ 20 + # 21 + # ## ## ### 22 + # # # # # # 23 + """ 24 + 25 + for y, line in enumerate(fileinput.input()): 26 + line = line.strip() 27 + nums = parse_nums(line) 28 + 29 + if not line: 30 + tiles[curr] = copy.deepcopy(board) 31 + board = new_table('.', width=10, height=10) 32 + 33 + if nums: 34 + curr = nums[0] 35 + sy = y 36 + continue 37 + 38 + for x, c in enumerate(line): 39 + board[y - sy - 1][x] = c 40 + 41 + tiles[curr] = copy.deepcopy(board) 42 + 43 + edges = Counter() 44 + piece_edges = defaultdict(set) 45 + 46 + # find edges: 47 + for id, t in tiles.items(): 48 + x = copy.deepcopy(t) 49 + for _ in range(4): 50 + row = ''.join(x[0]) 51 + edges[row] += 1 52 + edges[row[::-1]] += 1 53 + piece_edges[row].add(id) 54 + piece_edges[row[::-1]].add(id) 55 + x = rotated(x) 56 + 57 + # for c in edges.most_common(): 58 + # print c 59 + 60 + cands = [] 61 + 62 + for id, tile in tiles.items(): 63 + ones = 0 64 + for _ in range(4): 65 + row = ''.join(x[0]) 66 + if edges.get(row) == 1: 67 + ones += 1 68 + if edges.get(row[::-1]) == 1: 69 + ones += 1 70 + x = rotated(x) 71 + 72 + if ones >= 1: 73 + cands.append(id) 74 + 75 + # print ones 76 + 77 + graph = defaultdict(set) 78 + 79 + for p, e in piece_edges.items(): 80 + # print p, e 81 + for x in e: 82 + for y in e: 83 + graph[x].add(y) 84 + graph[y].add(x) 85 + 86 + 87 + tot = 1 88 + corners = [] 89 + for k, v in graph.items(): 90 + # print k, v 91 + if len(v) == 3: 92 + corners.append(k) 93 + # print k 94 + tot *= k 95 + 96 + # print tot 97 + 98 + puzzle = {} 99 + x = 0 100 + y = 0 101 + 102 + start = corners[0] 103 + puzzle[0, 0] = start 104 + o = [i for i in graph[start] if i != start] 105 + puzzle[0, 1] = o[0] 106 + puzzle[1, 0] = o[1] 107 + 108 + 109 + 110 + m = int(math.sqrt(len(tiles))) 111 + x = 2 112 + y = 2 113 + 114 + while x < m: 115 + for k, v in graph.items(): 116 + if x == m - 1: 117 + if len(v) == 3 and k in graph[puzzle[x - 1, 0]] and k not in puzzle.values(): 118 + puzzle[x, 0] = k 119 + x += 1 120 + break 121 + else: 122 + if len(v) == 4 and k in graph[puzzle[x - 1, 0]] and k not in puzzle.values(): 123 + puzzle[x, 0] = k 124 + x += 1 125 + break 126 + 127 + while y < m: 128 + for k, v in graph.items(): 129 + if y == m - 1: 130 + if len(v) == 3 and k in graph[puzzle[0, y - 1]] and k not in puzzle.values(): 131 + puzzle[0, y] = k 132 + y += 1 133 + break 134 + else: 135 + if len(v) == 4 and k in graph[puzzle[0, y - 1]] and k not in puzzle.values(): 136 + puzzle[0, y] = k 137 + y += 1 138 + break 139 + 140 + # print puzzle 141 + 142 + 143 + for y in range(1, m): 144 + for x in range(1, m): 145 + # print x, y 146 + for k, v in graph.items(): 147 + # print k, v, puzzle.get((x, y - 1)), puzzle.get((x - 1, y)) 148 + if puzzle.get((x, y - 1)) in v and puzzle.get((x - 1, y)) in v and k not in puzzle.values(): 149 + puzzle[x, y] = k 150 + 151 + assert len(puzzle) == len(tiles) 152 + 153 + print "Piece arrangement:" 154 + for y in range(m): 155 + print ' '.join(str(puzzle[x, y]) for x in range(m)) 156 + print 157 + 158 + PUZZLE = {} 159 + 160 + for y in range(m): 161 + for x in range(m): 162 + PUZZLE[x, y] = tiles[puzzle[x, y]] 163 + 164 + row = ''.join(PUZZLE[0, 0][-1]) 165 + col = ''.join(transposed(PUZZLE[0, 0])[-1]) 166 + 167 + # print row, piece_edges[row] 168 + # print col, piece_edges[col] 169 + 170 + for _ in range(2): 171 + for _ in range(4): 172 + row = ''.join(PUZZLE[0, 0][-1]) 173 + col = ''.join(transposed(PUZZLE[0, 0])[-1]) 174 + # print_grid(PUZZLE[0, 0]) 175 + # print 176 + if puzzle[0, 1] in piece_edges[row] and puzzle[1, 0] in piece_edges[col]: 177 + # print "good" 178 + break 179 + 180 + PUZZLE[0, 0] = rotated(PUZZLE[0, 0]) 181 + 182 + else: 183 + PUZZLE[0, 0] = transposed(PUZZLE[0, 0]) 184 + continue 185 + 186 + break 187 + 188 + # print puzzle[0, 0], puzzle[0, 1], puzzle[1, 0] 189 + # print_grid(PUZZLE[0, 0]) 190 + 191 + # x, 0 192 + for x in range(1, m): 193 + for _ in range(2): 194 + for _ in range(4): 195 + row = ''.join(transposed(PUZZLE[x, 0])[0]) 196 + # print "trying", row 197 + if row == ''.join(transposed(PUZZLE[x - 1, 0])[-1]): 198 + # print "good for x=", x 199 + # print_grid(PUZZLE[x, 0]) 200 + break 201 + 202 + PUZZLE[x, 0] = rotated(PUZZLE[x, 0]) 203 + 204 + else: 205 + PUZZLE[x, 0] = transposed(PUZZLE[x, 0]) 206 + continue 207 + 208 + break 209 + 210 + else: 211 + print "no match for", x 212 + 213 + # y, 0 214 + for y in range(1, m): 215 + for _ in range(2): 216 + for _ in range(4): 217 + row = ''.join(PUZZLE[0, y][0]) 218 + # print "trying", row 219 + if row == ''.join(PUZZLE[0, y - 1][-1]): 220 + # print "good for y=", y 221 + # print_grid(PUZZLE[0, y]) 222 + break 223 + 224 + PUZZLE[0, y] = rotated(PUZZLE[0, y]) 225 + 226 + else: 227 + PUZZLE[0, y] = transposed(PUZZLE[0, y]) 228 + continue 229 + 230 + break 231 + 232 + else: 233 + print "no match for", y 234 + 235 + 236 + for y in range(1, m): 237 + for x in range(1, m): 238 + for _ in range(2): 239 + for _ in range(4): 240 + col = ''.join(transposed(PUZZLE[x, y])[0]) 241 + row = ''.join(PUZZLE[x, y][0]) 242 + 243 + ocol = ''.join(transposed(PUZZLE[x - 1, y])[-1]) 244 + orow = ''.join(PUZZLE[x, y - 1][-1]) 245 + if row == orow and col == ocol: 246 + # print "good for {}, {}".format(x, y) 247 + # print_grid(PUZZLE[x, y]) 248 + break 249 + 250 + PUZZLE[x, y] = rotated(PUZZLE[x, y]) 251 + 252 + else: 253 + PUZZLE[x, y] = transposed(PUZZLE[x, y]) 254 + continue 255 + 256 + break 257 + 258 + else: 259 + print "no match for", x, y 260 + 261 + monster = [[None for _ in range(8 * m)] for _ in range(8 * m)] 262 + 263 + for y in range(m): 264 + for x in range(m): 265 + for j in range(8): 266 + for i in range(8): 267 + # print x, y, i, j 268 + monster[y*8 + j][x*8 + i] = PUZZLE[x, y][j + 1][i + 1] 269 + 270 + 271 + MONSTER = [] 272 + MONSTER.append(" # ") 273 + MONSTER.append("# ## ## ###") 274 + MONSTER.append(" # # # # # # ") 275 + 276 + minds = [] 277 + 278 + mx = 20 279 + my = 3 280 + for y in range(len(MONSTER)): 281 + for x, c in enumerate(MONSTER[y]): 282 + if c == '#': 283 + minds.append((x, y)) 284 + 285 + # print minds 286 + 287 + for _ in range(2): 288 + monster = rotated(monster) 289 + 290 + # print_grid(monster) 291 + 292 + for _ in range(2): 293 + for _ in range(4): 294 + m_count = 0 295 + occupied = set() 296 + for y in range(len(monster) - my): 297 + for x in range(len(monster[0]) - mx): 298 + if all(monster[y+j][x+i] == '#' for i, j in minds): 299 + # print "monster!" 300 + m_count += 1 301 + 302 + for i, j in minds: 303 + occupied.add((x+i, y+j)) 304 + 305 + if m_count > 1: 306 + # print m_count, occupied 307 + final_monster = {} 308 + tot = 0 309 + for y in range(m * 8): 310 + for x in range(m * 8): 311 + p = Point(x, y) 312 + if monster[y][x] == '#': 313 + if (x, y) not in occupied: 314 + tot += 1 315 + final_monster[p] = '~' 316 + else: 317 + final_monster[p] = 'O' 318 + 319 + else: 320 + final_monster[p] = ' ' 321 + 322 + print_grid(final_monster) 323 + print 324 + print "Water roughness:", tot 325 + 326 + monster = rotated(monster) 327 + 328 + monster = transposed(monster) 329 +
+173
2020/day20.py
··· 1 + import math 2 + import fileinput 3 + from itertools import permutations 4 + from collections import Counter, defaultdict 5 + from utils import mul, print_grid, transposed, rotated 6 + 7 + 8 + TILES = defaultdict(list) 9 + 10 + # Read problem input 11 + for line in fileinput.input(): 12 + if line.startswith('Tile'): 13 + id = int(line.replace(':', '').split(' ')[1]) 14 + elif len(line) > 5: 15 + TILES[id].append(line.strip()) 16 + 17 + 18 + # Map edges to pieces that share that edge 19 + piece_edges = defaultdict(set) 20 + 21 + for id, tile in TILES.items(): 22 + for _ in range(4): 23 + row = ''.join(tile[0]) 24 + piece_edges[row].add(id) 25 + piece_edges[row[::-1]].add(id) 26 + tile = rotated(tile) 27 + 28 + 29 + # Construct graph of neighbouring pieces 30 + graph = defaultdict(set) 31 + 32 + for neighbouring_pieces in piece_edges.values(): 33 + for x, y in permutations(neighbouring_pieces, 2): 34 + graph[x].add(y) 35 + 36 + 37 + # Corners are the only pieces that neighbour exactly 2 other tiles. 38 + corners = [tile for tile, neighs in graph.items() if len(neighs) == 2] 39 + print "Corner ID product:", mul(corners) 40 + print 41 + 42 + 43 + # Build up the full arrangement of the tiles. Arbitrarily pick 44 + # a corner to be the top-left, and assign its neighbours. 45 + puzzle = {} 46 + puzzle[0, 0] = corners[0] 47 + 48 + 49 + # Let `m` represent the m by m arrangement of tiles. 50 + m = int(math.sqrt(len(TILES))) 51 + 52 + 53 + # Assign tiles for the top row and left column by choosing from the subset 54 + # of tiles that contain either 2 or 3 neighbours (ie. edges and corners). 55 + # Make sure to not reuse tiles since that ruins this algorithm. 56 + edges = [tile for tile, neighs in graph.items() if len(neighs) == 3] 57 + 58 + for x in range(1, m): 59 + for tile in edges + corners: 60 + if tile in graph[puzzle[x - 1, 0]] and tile not in puzzle.values(): 61 + puzzle[x, 0] = tile 62 + 63 + for y in range(1, m): 64 + for tile in edges + corners: 65 + if tile in graph[puzzle[0, y - 1]] and tile not in puzzle.values(): 66 + puzzle[0, y] = tile 67 + 68 + 69 + # Now assign the remaining tiles. Since the previous tiles were already filled 70 + # in, we can check the left and top neighbour for every candidate location, 71 + # which will uniquely match a single tile's neighbour list. 72 + for y in range(1, m): 73 + for x in range(1, m): 74 + for tile, neighbours in graph.items(): 75 + left = puzzle[x - 1, y] 76 + top = puzzle[x, y - 1] 77 + 78 + if left in neighbours and top in neighbours and tile not in puzzle.values(): 79 + puzzle[x, y] = tile 80 + 81 + print "Resolved piece arrangement:" 82 + for y in range(m): 83 + print ' '.join(str(puzzle[x, y]) for x in range(m)) 84 + print 85 + 86 + 87 + # Now we need to actually orient all the pieces. Let `PUZZLE` be a dictionary 88 + # that maps tile locations to 2D arrays representing the pieces themselves. 89 + PUZZLE = {pos: TILES[id] for pos, id in puzzle.items()} 90 + 91 + 92 + # Define some helper functions for manipulating tiles. 93 + def orientations(tile): 94 + """Generates all 8 orientations of a 2D array.""" 95 + for _ in range(2): 96 + for _ in range(4): 97 + yield tile 98 + tile = rotated(tile) 99 + 100 + tile = transposed(tile) 101 + 102 + def aligned(a, b): 103 + """Returns if the final row or col of `a` matches the first row or col of `b`""" 104 + return ( 105 + ''.join(a[-1]) == ''.join(b[0]) or 106 + ''.join(transposed(a)[-1]) == ''.join(transposed(b)[0]) 107 + ) 108 + 109 + 110 + # We need to get the top-left corner into an orientation where its bottom and 111 + # right edges match the neighbours based on the arrangement we determined above. 112 + for tile in orientations(PUZZLE[0, 0]): 113 + bottom = ''.join(tile[-1]) 114 + right = ''.join(transposed(tile)[-1]) 115 + 116 + if puzzle[0, 1] in piece_edges[bottom] and puzzle[1, 0] in piece_edges[right]: 117 + PUZZLE[0, 0] = tile 118 + break 119 + 120 + 121 + # Align remaining tiles by matching them up via their top and left edges, if present. 122 + for y in range(m): 123 + for x in range(m): 124 + if x == 0 and y == 0: 125 + continue 126 + 127 + for tile in orientations(PUZZLE[x, y]): 128 + if (x == 0 or aligned(PUZZLE[x - 1, y], tile)) and (y == 0 or aligned(PUZZLE[x, y - 1], tile)): 129 + PUZZLE[x, y] = tile 130 + break 131 + 132 + 133 + # Process the puzzle to form the image (removing borders of tiles). 134 + # Store as 2D array instead of dictionary so we can re-orient it. 135 + IMG = [[None for _ in range(8 * m)] for _ in range(8 * m)] 136 + for (x, y), tile in PUZZLE.items(): 137 + for j in range(8): 138 + for i in range(8): 139 + IMG[y*8 + j][x*8 + i] = tile[j + 1][i + 1] 140 + 141 + 142 + # Get the offset positions for the sea monster components. 143 + MONSTER = """\ 144 + # 145 + # ## ## ### 146 + # # # # # # 147 + """ 148 + 149 + m_pos = [] 150 + for y, row in enumerate(MONSTER.split('\n')): 151 + for x, c in enumerate(row): 152 + if c == '#': 153 + m_pos.append((x, y)) 154 + 155 + 156 + # Search for sea monsters! 157 + for image in orientations(IMG): 158 + occupied = set() 159 + for y in range(m * 8 - max(y for x, y in m_pos)): 160 + for x in range(m * 8 - max(x for x, y in m_pos)): 161 + if all(image[y+j][x+i] == '#' for i, j in m_pos): 162 + for i, j in m_pos: 163 + occupied.add((x+i, y+j)) 164 + 165 + if occupied: 166 + final_img = {} 167 + for y in range(m * 8): 168 + for x in range(m * 8): 169 + if image[y][x] == '#': 170 + final_img[x, y] = 'O' if (x, y) in occupied else '~' 171 + 172 + print_grid(final_img) 173 + print "\nWater roughness:", sum(1 for c in final_img.values() if c == '~')
+62
2020/day21.py
··· 1 + import fileinput 2 + from utils import resolve_mapping 3 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 4 + 5 + foods = [] 6 + 7 + for y, line in enumerate(fileinput.input()): 8 + line = line.strip() 9 + 10 + ingredients, allergens = line.split(' (') 11 + allergens = allergens.replace(')', '').replace(',', '').split(' ')[1:] 12 + 13 + foods.append((ingredients.split(' '), allergens)) 14 + 15 + ing_to_all = defaultdict(set) 16 + all_to_ing = defaultdict(set) 17 + 18 + for ingredients, allergens in foods: 19 + for i in ingredients: 20 + for a in allergens: 21 + ing_to_all[i].add(a) 22 + all_to_ing[a].add(i) 23 + 24 + candidates = defaultdict(set) 25 + resolved_ing = set() 26 + 27 + for a in all_to_ing: 28 + poss = [] 29 + for ingredients, allergens in foods: 30 + if a in allergens: 31 + poss.append(ingredients) 32 + 33 + if poss: 34 + resolve = set(poss[0]) 35 + for p in poss[1:]: 36 + resolve &= set(p) 37 + 38 + for r in resolve: 39 + candidates[a].add(r) 40 + resolved_ing.add(r) 41 + else: 42 + print "empty poss", a 43 + 44 + good = set(ing_to_all) - resolved_ing 45 + print len(good) 46 + 47 + tot = 0 48 + for ingredients, allergens in foods: 49 + for i in ingredients: 50 + if i in good: 51 + tot += 1 52 + 53 + 54 + print tot 55 + 56 + resolved = resolve_mapping(candidates) 57 + 58 + l = [] 59 + for r in sorted(resolved): 60 + l.append(resolved[r]) 61 + 62 + print ','.join(l)
+92
2020/day22.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import copy # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 10 + 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA 12 + from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 13 + from utils import new_table, transposed, rotated # NOQA 14 + from utils import md5, sha256, knot_hash # NOQA 15 + from utils import VOWELS, CONSONANTS # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 17 + 18 + tot = 0 19 + res = [] 20 + board = {} 21 + table = new_table(None, width=2, height=4) 22 + 23 + p1 = deque() 24 + p2 = deque() 25 + 26 + is1 = True 27 + 28 + for y, line in enumerate(fileinput.input()): 29 + line = line.strip() 30 + nums = parse_nums(line) 31 + data = parse_line(r'', line) 32 + 33 + if ':' in line: 34 + continue 35 + 36 + if not line: 37 + is1 = False 38 + 39 + if nums: 40 + if is1: 41 + p1.append(nums[0]) 42 + else: 43 + p2.append(nums[0]) 44 + 45 + 46 + def serialize(p1, p2): 47 + a = ','.join(str(x) for x in p1) 48 + b = ','.join(str(x) for x in p2) 49 + return a + '~' + b 50 + 51 + SEEN = set() 52 + 53 + depth = 0 54 + 55 + @memoize 56 + def combat(p1, p2): 57 + # print "triggered new combat", p1, p2 58 + while p1 and p2: 59 + state = serialize(p1, p2) 60 + if state in SEEN: 61 + return p1, True 62 + 63 + SEEN.add(state) 64 + 65 + a = p1.popleft() 66 + b = p2.popleft() 67 + 68 + if len(p1) >= a and len(p2) >= b: 69 + # print "triggering combat for", a, b 70 + p1_win = combat(deque(list(p1)[:a]), deque(list(p2)[:b]))[1] 71 + else: 72 + p1_win = a > b 73 + 74 + if p1_win: 75 + p1.append(a) 76 + p1.append(b) 77 + else: 78 + p2.append(b) 79 + p2.append(a) 80 + 81 + return p1 if p1 else p2, True if p1 else False 82 + 83 + 84 + deck, winner = combat(p1, p2) 85 + print deck, winner 86 + 87 + 88 + ans = 0 89 + for i, a in enumerate(reversed(deck), start = 1): 90 + ans += i * a 91 + 92 + print ans
+68
2020/day23.py
··· 1 + NUM_CUPS = 1000000 2 + ITERS = 10000000 3 + 4 + class Cup: 5 + def __init__(self, n): 6 + self.n = n 7 + 8 + def __repr__(self): 9 + return "{} -> ({}) -> {}".format(self.prev.n, self.n, self.next.n) 10 + 11 + labeling = [int(x) for x in raw_input()] 12 + cup_ptrs = {} 13 + 14 + for i in range(1, NUM_CUPS + 1): 15 + cup_ptrs[i] = Cup(i) 16 + 17 + curr_cup = None 18 + prev = None 19 + for n in labeling: 20 + if curr_cup is None: 21 + curr_cup = cup_ptrs[n] 22 + prev = curr_cup 23 + else: 24 + cup_ptrs[n].prev = prev 25 + prev.next = cup_ptrs[n] 26 + prev = cup_ptrs[n] 27 + 28 + for n in range(len(labeling) + 1, NUM_CUPS + 1): 29 + cup_ptrs[n].prev = prev 30 + prev.next = cup_ptrs[n] 31 + prev = cup_ptrs[n] 32 + 33 + curr_cup.prev = prev 34 + prev.next = curr_cup 35 + 36 + for move in range(ITERS): 37 + curr = curr_cup.n 38 + 39 + # Pick up three cups 40 + a = curr_cup.next 41 + b = a.next 42 + c = b.next 43 + 44 + # Cup spacing adjusted 45 + a.prev.next = c.next 46 + c.next.prev = a.prev 47 + 48 + dval = curr - 1 49 + if dval == 0: 50 + dval = NUM_CUPS 51 + while dval == a.n or dval == b.n or dval == c.n: 52 + dval -= 1 53 + if dval == 0: 54 + dval = NUM_CUPS 55 + 56 + dest_cup = cup_ptrs[dval] 57 + tmp_cup = dest_cup.next 58 + 59 + dest_cup.next = a 60 + a.prev = dest_cup 61 + 62 + c.next = tmp_cup 63 + tmp_cup.prev = c 64 + 65 + # Move current cup to the next cup. 66 + curr_cup = curr_cup.next 67 + 68 + print cup_ptrs[1].next.n * cup_ptrs[1].next.next.n
+62
2020/day24.py
··· 1 + import fileinput 2 + from collections import defaultdict 3 + 4 + HEX_DIRS = { 5 + 'e': (1, -1, 0), 6 + 'ne': (1, 0, -1), 7 + 'se': (0, -1, 1), 8 + 'w': (-1, 1, 0), 9 + 'sw': (-1, 0, 1), 10 + 'nw': (0, 1, -1), 11 + } 12 + 13 + def hex_neighbours(x, y, z): 14 + for dx, dy, dz in HEX_DIRS.values(): 15 + yield x + dx, y + dy, z + dz 16 + 17 + 18 + TILES = defaultdict(bool) 19 + 20 + for line in fileinput.input(): 21 + x = 0 22 + y = 0 23 + z = 0 24 + curr = '' 25 + 26 + for c in line: 27 + curr += c 28 + if curr in HEX_DIRS: 29 + dx, dy, dz = HEX_DIRS[curr] 30 + x += dx 31 + y += dy 32 + z += dz 33 + curr = '' 34 + 35 + TILES[x, y, z] = not TILES[x, y, z] 36 + 37 + print "Part 1:", sum(TILES.values()) 38 + 39 + for day in range(1, 100 + 1): 40 + new_tiles = defaultdict(bool) 41 + 42 + for old_pos in TILES.keys(): 43 + for pos in hex_neighbours(*old_pos): 44 + neighs = 0 45 + for n in hex_neighbours(*pos): 46 + if TILES[n]: 47 + neighs += 1 48 + 49 + if TILES[pos] and (neighs == 0 or neighs > 2): 50 + pass 51 + elif not TILES[pos] and neighs == 2: 52 + new_tiles[pos] = True 53 + else: 54 + if TILES[pos]: 55 + new_tiles[pos] = True 56 + 57 + TILES = new_tiles 58 + 59 + print "Part 2:", sum(TILES.values()) 60 + 61 + 62 +
+19
2020/day25.py
··· 1 + import sys 2 + import fileinput 3 + 4 + 5 + MOD = 20201227 6 + KEYS = [int(n) for n in fileinput.input()] 7 + 8 + card_pub = KEYS[0] 9 + door_pub = KEYS[1] 10 + 11 + 12 + for loop_size in range(1, MOD + 1): 13 + val = pow(7, loop_size, MOD) 14 + if val == card_pub: 15 + print pow(door_pub, loop_size, MOD) 16 + break 17 + elif val == door_pub: 18 + print pow(card_pub, loop_size, MOD) 19 + break
+51
2020/search.py
··· 1 + def bfs(start, graph): 2 + from collections import deque 3 + 4 + max_depth = 0 5 + depths = {} 6 + horizon = deque([(start, 0)]) # node, depth 7 + seen = {start: None} 8 + 9 + # TODO 10 + def is_goal(node): 11 + return graph.get(node) == 'G' 12 + 13 + def gen_neighbours(node): 14 + for n in node.neighbours_4(): 15 + if graph.get(n, ' ') != ' ': 16 + yield n 17 + 18 + 19 + while horizon: 20 + node, depth = horizon.popleft() # pop() for DFS 21 + depths[node] = depth 22 + 23 + if depth > max_depth: 24 + max_depth = depth 25 + print "BFS @ {}; depth={}, horizon={}, seen={}".format(node, depth, len(horizon), len(seen)) 26 + 27 + for new in gen_neighbours(node): 28 + if new in seen: 29 + continue 30 + 31 + seen[new] = node 32 + horizon.append((new, depth + 1)) 33 + 34 + if is_goal(new): 35 + print "FOUND GOAL", new, depth + 1 36 + path = [] 37 + curr = new 38 + while curr is not None: 39 + path.append(curr) 40 + curr = seen[curr] 41 + for p in reversed(path): 42 + print p 43 + pass 44 + print "FOUND GOAL", new, depth + 1 45 + return depth + 1 46 + 47 + print "FLOOD FILL COMPLETE, max_depth={}, seen={}".format(max_depth, len(seen)) 48 + return depths 49 + 50 + start = Point(0, 0) 51 + bfs(start, board)
+16 -6
2020/starter.py
··· 2 2 import sys # NOQA 3 3 import re # NOQA 4 4 import math # NOQA 5 + import copy # NOQA 5 6 import fileinput 6 7 from string import ascii_uppercase, ascii_lowercase # NOQA 7 8 from collections import Counter, defaultdict, deque, namedtuple # NOQA 8 9 from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 9 10 10 - from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA 11 12 from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 12 13 from utils import new_table, transposed, rotated # NOQA 13 14 from utils import md5, sha256, knot_hash # NOQA 14 15 from utils import VOWELS, CONSONANTS # NOQA 15 - from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 16 17 17 18 # Itertools Functions: 18 19 # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD ··· 20 21 # combinations('ABCD', 2) AB AC AD BC BD CD 21 22 # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 22 23 23 - total = 0 24 - result = [] 24 + tot = 0 25 + res = [] 26 + board = {} 25 27 table = new_table(None, width=2, height=4) 26 28 27 - for i, line in enumerate(fileinput.input()): 29 + # Uncomment for multi-group style inputs. :c 30 + # data = ''.join([line for line in fileinput.input()]) 31 + # groups = [g.split('\n') for g in data.split('\n\n')] 32 + 33 + for y, line in enumerate(fileinput.input()): 28 34 line = line.strip() 29 35 nums = parse_nums(line) 30 36 data = parse_line(r'', line) 31 37 32 - if i == 0: 38 + for x, c in enumerate(line): 39 + board[Point(x, y)] = c 40 + 41 + if y == 0: 33 42 print(data) 43 +
+147 -7
2020/utils.py
··· 2 2 import math 3 3 import hashlib 4 4 import operator 5 + import copy 6 + from collections import Counter 5 7 from functools import total_ordering 6 8 7 9 ··· 76 78 return a * b / gcd(a, b) 77 79 78 80 81 + def egcd(a, b): 82 + x0, x1, y0, y1 = 1, 0, 0, 1 83 + while b: 84 + q, a, b = a // b, b, a % b 85 + x0, x1 = x1, x0 - q * x1 86 + y0, y1 = y1, y0 - q * y1 87 + return a, x0, y0 88 + 89 + def modinv(a, n): 90 + g, x, _ = egcd(a, n) 91 + if g == 1: 92 + return x % n 93 + else: 94 + raise ValueError("%d is not invertible mod %d" % (a, n)) 95 + 96 + def crt(rems, mods): 97 + ''' Solve a system of modular equivalences via the Chinese Remainder Theorem. 98 + Does not require pairwise coprime moduli. ''' 99 + 100 + # copy inputs 101 + orems, omods = rems, mods 102 + rems = list(rems) 103 + mods = list(mods) 104 + 105 + newrems = [] 106 + newmods = [] 107 + 108 + for i in range(len(mods)): 109 + for j in range(i+1, len(mods)): 110 + g = gcd(mods[i], mods[j]) 111 + if g == 1: 112 + continue 113 + if rems[i] % g != rems[j] % g: 114 + raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g)) 115 + mods[j] //= g 116 + 117 + while 1: 118 + # transfer any remaining gcds to mods[j] 119 + g = gcd(mods[i], mods[j]) 120 + if g == 1: 121 + break 122 + mods[i] //= g 123 + mods[j] *= g 124 + 125 + if mods[i] == 1: 126 + continue 127 + 128 + newrems.append(rems[i] % mods[i]) 129 + newmods.append(mods[i]) 130 + 131 + rems, mods = newrems, newmods 132 + 133 + # standard CRT 134 + s = 0 135 + n = 1 136 + for k in mods: 137 + n *= k 138 + 139 + for i in range(len(mods)): 140 + ni = n // mods[i] 141 + s += rems[i] * modinv(ni, mods[i]) * ni 142 + return s % n, n 143 + 144 + 79 145 def min_max_xy(points): 80 146 if len(points) == 0: 81 147 return None, None, None, None ··· 93 159 return min_x, max_x, min_y, max_y 94 160 95 161 96 - def print_grid(grid, f=None): 162 + def print_grid(grid, f=None, quiet=False): 97 163 if f is None: 98 164 f = lambda x: x # NOQA 99 165 166 + counts = Counter() 167 + serialized = [] 168 + 100 169 if type(grid) is dict: 101 170 positions = grid.keys() 102 171 min_x, max_x, min_y, max_y = min_max_xy(positions) 103 172 if type(positions[0]) is tuple: 104 173 for y in range(min_y, max_y + 1): 105 - print ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1)) 174 + row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1)) 175 + if not quiet: 176 + print row 177 + serialized.append(row) 178 + for c in row: 179 + counts[c] += 1 180 + 106 181 else: 107 182 # (x, y) => point 108 183 for y in range(min_y, max_y + 1): 109 - print ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1)) 184 + row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1)) 185 + if not quiet: 186 + print row 187 + serialized.append(row) 188 + for c in row: 189 + counts[c] += 1 110 190 else: 111 191 for y in range(len(grid)): 112 - print ''.join(f(grid[y][x]) for x in range(len(grid[0]))) 192 + row = ''.join(f(grid[y][x]) for x in range(len(grid[0]))) 193 + if not quiet: 194 + print row 195 + serialized.append(row) 196 + for c in row: 197 + counts[c] += 1 198 + 199 + # if not quiet: 200 + # print "height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y) 201 + # print "width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x) 202 + # print "Statistics:" 203 + # for item, num in counts.most_common(): 204 + # print "{}: {}".format(item, num) 205 + 206 + return serialized 207 + 208 + def resolve_mapping(candidates): 209 + resolved = {} 210 + 211 + # Ensure the mapping is key -> set(values). 212 + candidates_map = {} 213 + for k, v in candidates.items(): 214 + candidates_map[k] = set(v) 215 + 216 + while len(resolved) < len(candidates_map): 217 + for candidate in candidates_map: 218 + if len(candidates_map[candidate]) == 1 and candidate not in resolved: 219 + r = candidates_map[candidate].pop() 220 + for c in candidates_map: 221 + candidates_map[c].discard(r) 222 + 223 + resolved[candidate] = r 224 + break 225 + 226 + return resolved 113 227 114 228 115 229 def memoize(f): ··· 117 231 cache = {} 118 232 119 233 def _mem_fn(*args): 120 - if args not in cache: 121 - cache[args] = f(*args) 122 - return cache[args] 234 + hargs = (','.join(str(x) for x in args)) 235 + if hargs not in cache: 236 + cache[hargs] = f(*args) 237 + return cache[hargs] 123 238 124 239 _mem_fn.cache = cache 125 240 return _mem_fn ··· 253 368 return math.atan2(self.y, self.x) 254 369 return math.atan2(self.y - to.y, self.x - to.x) 255 370 371 + def rotate(self, turns): 372 + """Returns the rotation of the Point around (0, 0) `turn` times clockwise.""" 373 + turns = turns % 4 374 + 375 + if turns == 1: 376 + return Point(self.y, -self.x) 377 + elif turns == 2: 378 + return Point(-self.x, -self.y) 379 + elif turns == 3: 380 + return Point(-self.y, self.x) 381 + else: 382 + return self 383 + 256 384 @property 257 385 def manhattan(self): 258 386 return abs(self.x) + abs(self.y) ··· 264 392 def neighbours_4(self): 265 393 return [self + p for p in DIRS_4] 266 394 395 + def neighbors_4(self): 396 + return self.neighbours_4() 397 + 398 + def neighbours(self): 399 + return self.neighbours_4() 400 + 401 + def neighbors(self): 402 + return self.neighbours() 403 + 267 404 def neighbours_8(self): 268 405 return [self + p for p in DIRS_8] 406 + 407 + def neighbors_8(self): 408 + return self.neighbours_8() 269 409 270 410 271 411 DIRS_4 = DIRS = [
+64
2020/vm.py
··· 1 + import os # NOQA 2 + import sys # NOQA 3 + import re # NOQA 4 + import math # NOQA 5 + import time # NOQA 6 + import fileinput 7 + from string import ascii_uppercase, ascii_lowercase # NOQA 8 + from collections import Counter, defaultdict, deque, namedtuple # NOQA 9 + from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA 10 + 11 + from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA 12 + from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA 13 + from utils import new_table, transposed, rotated # NOQA 14 + from utils import md5, sha256, knot_hash # NOQA 15 + from utils import VOWELS, CONSONANTS # NOQA 16 + from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) 17 + 18 + # Itertools Functions: 19 + # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD 20 + # permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC 21 + # combinations('ABCD', 2) AB AC AD BC BD CD 22 + # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD 23 + 24 + TAPE = [] 25 + 26 + for line in fileinput.input(): 27 + ins = line.split(' ')[0] 28 + TAPE.append([ins, parse_nums(line)]) 29 + 30 + 31 + def emulate(tape, pc=0, acc=0, debug=False): 32 + """Returns (acc, pc, regs, loop_detected)""" 33 + breakpoints = set([]) 34 + seen = Counter() 35 + regs = {} 36 + while pc < len(tape): 37 + if pc in seen: 38 + pass 39 + # print "Exiting on repeat PC:", pc 40 + # return (acc, pc, regs, True) 41 + 42 + seen[pc] += 1 43 + 44 + ins, ops = tape[pc] 45 + if debug: 46 + if pc in breakpoints: 47 + print '-------' 48 + time.sleep(0.001) 49 + print "[{:04d} @ {:03d}] {} {}\t\t{}\t\t{}".format(pc, seen[pc], ins, ops, acc, regs) 50 + 51 + if ins == 'acc': 52 + acc += ops[0] 53 + pc += 1 54 + elif ins == 'jmp': 55 + pc += ops[0] 56 + elif ins == 'nop': 57 + pc += 1 58 + else: 59 + print "UNKNOWN OPCODE", ins 60 + pc += 1 61 + 62 + return (acc, pc, regs, False) 63 + 64 + emulate(TAPE, debug=True)