···11+import fileinput
22+33+nums = [int(x) for x in fileinput.input()]
44+55+part_1 = None
66+part_2 = None
77+88+for m in nums:
99+ for n in nums:
1010+ for o in nums:
1111+ if m + n == 2020:
1212+ part_1 = m * n
1313+ elif m + n + o == 2020:
1414+ part_2 = m * n * o
1515+1616+print "Part 1:", part_1
1717+print "Part 2:", part_2
···11+import fileinput
22+33+44+grid = [line.strip() for line in fileinput.input()]
55+66+max_y = len(grid)
77+max_x = len(grid[0])
88+99+part_1 = 0
1010+part_2 = 1
1111+1212+for xs, ys in ((1, 1), (3, 1), (5, 1), (7, 1), (1, 2)):
1313+ x = 0
1414+ y = 0
1515+ tot = 0
1616+1717+ while y < max_y:
1818+ if grid[y][x] == '#':
1919+ tot += 1
2020+2121+ x = (x + xs) % max_x
2222+ y += ys
2323+2424+ part_2 *= tot
2525+2626+ if (xs, ys) == (3, 1):
2727+ part_1 = tot
2828+2929+print part_1
3030+print part_2
+50
2020/day04.py
···11+import fileinput
22+from collections import OrderedDict
33+44+def validate_hgt(hgt):
55+ if hgt.endswith('cm'):
66+ return 150 <= int(hgt[:-2]) <= 193
77+ elif hgt.endswith('in'):
88+ return 59 <= int(hgt[:-2]) <= 76
99+1010+ return False
1111+1212+VALIDATION_FNS = {
1313+ 'byr': lambda v: 1920 <= int(v) <= 2002,
1414+ 'iyr': lambda v: 2010 <= int(v) <= 2020,
1515+ 'eyr': lambda v: 2020 <= int(v) <= 2030,
1616+ 'hgt': validate_hgt,
1717+ 'hcl': lambda v: len(v) == 7 and v[0] == '#' and all(c in 'abcdef1234567890' for c in v[1:]),
1818+ 'ecl': lambda v: v in ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth'],
1919+ 'pid': lambda v: len(v) == 9 and all(c in '1234567890' for c in v),
2020+}
2121+2222+# Parse problem input
2323+passports = []
2424+port = OrderedDict()
2525+2626+for line in fileinput.input():
2727+ line = line.strip()
2828+ if not line:
2929+ passports.append(port)
3030+ port = OrderedDict()
3131+ else:
3232+ parts = [x.split(':') for x in line.split()]
3333+ for a, b in parts:
3434+ port[a] = b
3535+3636+passports.append(port)
3737+3838+part_1 = 0
3939+part_2 = 0
4040+4141+for p in passports:
4242+ if all(k in p for k in VALIDATION_FNS):
4343+ part_1 += 1
4444+4545+ if all(VALIDATION_FNS.get(k, lambda x: True)(v) for k, v in p.items()):
4646+ part_2 += 1
4747+4848+print part_1
4949+print part_2
5050+
+24
2020/day05.py
···11+import fileinput
22+from collections import Counter
33+44+55+seen = set()
66+max_seat_id = 0
77+min_row = 1000
88+max_row = 0
99+1010+for line in fileinput.input():
1111+ row = int(line[:7].replace('F', '0').replace('B', '1'), 2)
1212+ col = int(line[7:10].replace('L', '0').replace('R', '1'), 2)
1313+1414+ min_row = min(row, min_row)
1515+ max_row = max(row, max_row)
1616+ max_seat_id = max(row * 8 + col, max_seat_id)
1717+ seen.add((row, col))
1818+1919+print "Highest seat ID:", max_seat_id
2020+2121+for row in range(min_row + 1, max_row - 1):
2222+ for col in range(8):
2323+ if (row, col) not in seen:
2424+ print "Your seat ID:", row * 8 + col
+22
2020/day06.py
···11+import fileinput
22+from string import ascii_lowercase
33+44+data = ''.join([line for line in fileinput.input()])
55+groups = [g.split('\n') for g in data.split('\n\n')]
66+77+part_1 = 0
88+part_2 = 0
99+1010+for group in groups:
1111+ anyone = set()
1212+ everyone = set(ascii_lowercase)
1313+1414+ for person in group:
1515+ anyone |= set(person)
1616+ everyone &= set(person)
1717+1818+ part_1 += len(anyone)
1919+ part_2 += len(everyone)
2020+2121+print part_1
2222+print part_2
+49
2020/day07.py
···11+import fileinput
22+from collections import defaultdict
33+from utils import parse_line
44+55+GOAL = 'shiny gold'
66+77+graph = defaultdict(set)
88+99+for i, line in enumerate(fileinput.input()):
1010+ line = line.strip()
1111+ color, rest = parse_line(r'(\w+ \w+) bags contain (.+)+', line)
1212+ rest = rest.replace('.', '').split(',')
1313+1414+ for r in rest:
1515+ if r != 'no other bags':
1616+ parts = r.split()
1717+ new_color = ' '.join(parts[1:3])
1818+ graph[color].add((new_color, int(parts[0])))
1919+2020+2121+def dfs_1(node):
2222+ if node == GOAL:
2323+ return True
2424+2525+ return any(dfs_1(n) for n, _ in graph[node])
2626+2727+2828+def dfs_2(node, count=1):
2929+ tot = 1
3030+3131+ for nxt, amt in graph[node]:
3232+ tot += dfs_2(nxt, amt)
3333+3434+ return count * tot
3535+3636+part_1 = 0
3737+3838+keys = graph.keys()
3939+4040+for bag in keys:
4141+ print bag
4242+ if dfs_1(bag):
4343+ part_1 += 1
4444+ print "good"
4545+4646+print part_1 - 1
4747+4848+4949+print dfs_2(GOAL) - 1
+49
2020/day08.py
···11+import fileinput
22+from copy import deepcopy
33+from utils import parse_nums
44+55+TAPE = []
66+77+for line in fileinput.input():
88+ ins = line.split(' ')[0]
99+ TAPE.append([ins, parse_nums(line)])
1010+1111+1212+def emulate(tape, pc=0, acc=0):
1313+ """Returns (acc, pc, loop_detected)"""
1414+ seen = set()
1515+ while pc < len(tape):
1616+ if pc in seen:
1717+ return (acc, pc, True)
1818+ else:
1919+ seen.add(pc)
2020+2121+ ins, ops = tape[pc]
2222+2323+ if ins == 'acc':
2424+ acc += ops[0]
2525+ pc += 1
2626+ elif ins == 'jmp':
2727+ pc += ops[0]
2828+ else:
2929+ pc += 1
3030+3131+ return (acc, pc, False)
3232+3333+3434+print "ACC at repeated instruction:", emulate(TAPE)[0]
3535+3636+for i in range(len(TAPE)):
3737+ tape = deepcopy(TAPE)
3838+3939+ if tape[i][0] == 'acc':
4040+ continue
4141+ else:
4242+ if tape[i][0] == 'jmp':
4343+ tape[i][0] = 'nop'
4444+ else: # ins == 'nop'
4545+ tape[i][0] = 'jmp'
4646+4747+ acc, pc, loop_detected = emulate(tape)
4848+ if not loop_detected:
4949+ print "ACC with modified termination:", acc
+22
2020/day09.py
···11+import fileinput
22+from itertools import permutations
33+44+SEQ = [int(x) for x in fileinput.input()]
55+LEN = 25
66+77+for i in range(LEN, len(SEQ)):
88+ for x, y in permutations(SEQ[i-LEN:i], 2):
99+ if x + y == SEQ[i]:
1010+ break
1111+ else:
1212+ INVALID = SEQ[i]
1313+ print "Part 1:", INVALID
1414+ break
1515+1616+for n in range(2, len(SEQ)):
1717+ tot = 0
1818+ for i in range(len(SEQ)-n):
1919+ tot = sum(SEQ[i:i+n])
2020+ if tot == INVALID:
2121+ print "Part 2:", min(SEQ[i:i+n]) + max(SEQ[i:i+n])
2222+
+39
2020/day10.py
···11+import fileinput
22+from collections import Counter
33+from utils import memoize
44+55+NUMS = [int(x) for x in fileinput.input()]
66+77+NUMS.append(0)
88+NUMS.append(max(NUMS) + 3)
99+NUMS.sort()
1010+1111+deltas = Counter()
1212+curr = 0
1313+1414+for n in NUMS:
1515+ deltas[n - curr] += 1
1616+ curr = n
1717+1818+print "Part 1:", deltas[1] * deltas[3]
1919+2020+2121+@memoize
2222+def dp(i):
2323+ if i >= len(NUMS) - 1:
2424+ return 1
2525+2626+ ways = 0
2727+2828+ if i + 1 < len(NUMS) and NUMS[i + 1] - NUMS[i] <= 3:
2929+ ways += dp(i + 1)
3030+3131+ if i + 2 < len(NUMS) and NUMS[i + 2] - NUMS[i] <= 3:
3232+ ways += dp(i + 2)
3333+3434+ if i + 3 < len(NUMS) and NUMS[i + 3] - NUMS[i] <= 3:
3535+ ways += dp(i + 3)
3636+3737+ return ways
3838+3939+print "Part 2:", dp(0)
+119
2020/day11.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from heapq import heappush, heappop, heapify
99+from collections import Counter, defaultdict, deque, namedtuple # NOQA
1010+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1111+1212+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1313+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1414+from utils import new_table, transposed, rotated # NOQA
1515+from utils import md5, sha256, knot_hash # NOQA
1616+from utils import VOWELS, CONSONANTS # NOQA
1717+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1818+1919+# Itertools Functions:
2020+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2121+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2222+# combinations('ABCD', 2) AB AC AD BC BD CD
2323+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2424+2525+tot = 0
2626+res = []
2727+table = new_table(None, width=2, height=4)
2828+graph = {}
2929+3030+# Uncomment for multi-group style inputs. :c
3131+# data = ''.join([line for line in fileinput.input()])
3232+# groups = [g.split('\n') for g in data.split('\n\n')]
3333+3434+for i, line in enumerate(fileinput.input()):
3535+ line = line.strip()
3636+ nums = parse_nums(line)
3737+ data = parse_line(r'', line)
3838+3939+ for j, c in enumerate(line):
4040+ graph[Point(j, i)] = c
4141+4242+ res.append(line)
4343+4444+ if i == 0:
4545+ print(data)
4646+4747+max_x = len(res[0])
4848+max_y = len(res)
4949+5050+print graph
5151+print res
5252+5353+state = graph
5454+5555+while True:
5656+ new_state = {}
5757+5858+ for y in range(max_y):
5959+ for x in range(max_x):
6060+ p = Point(x, y)
6161+ occ = 0
6262+ # for n in p.neighbours_8():
6363+ # if state.get(n, '.') == '#':
6464+ # occ += 1
6565+6666+ for d in DIRS_8:
6767+ np = p + d
6868+ # np = p
6969+ while 0 <= np.x < max_x and 0 <= np.y < max_y:
7070+ if state.get(np, '.') == '.':
7171+ np += d
7272+ # break
7373+ elif state.get(np, '.') == '#':
7474+ occ += 1
7575+ break
7676+ else:
7777+ break
7878+7979+8080+ if state[p] == 'L' and occ == 0:
8181+ new_state[p] = '#'
8282+8383+ elif state[p] == '#' and occ >= 5:
8484+ new_state[p] = 'L'
8585+8686+ else:
8787+ new_state[p] = state[p]
8888+8989+9090+9191+ a = ''
9292+ b = ''
9393+ for y in range(max_y):
9494+ for x in range(max_x):
9595+ p = Point(x, y)
9696+ # print state[p],
9797+ a += state[p]
9898+ b += new_state[p]
9999+100100+ # print
101101+102102+ # print
103103+ # print
104104+105105+ if a == b:
106106+ tot = 0
107107+ for y in range(max_y):
108108+ for x in range(max_x):
109109+ if new_state[Point(x, y)] == '#':
110110+ tot += 1
111111+112112+ print tot
113113+114114+ break
115115+116116+117117+ state = new_state
118118+119119+
+35
2020/day12.py
···11+import fileinput
22+from utils import Point, DIRS
33+44+DIR_MAP = 'NESW'
55+66+p1 = Point(0, 0)
77+p2 = Point(0, 0)
88+wp = Point(10, 1)
99+1010+facing = 1 # start at 1 to match DIRS
1111+1212+for line in fileinput.input():
1313+ d = line[0]
1414+ n = int(line[1:])
1515+1616+ if d in DIR_MAP:
1717+ delta = DIRS[DIR_MAP.index(d)] * n
1818+ p1 += delta
1919+ wp += delta
2020+2121+ elif d == 'L' or d == 'R':
2222+ turns = n // 90
2323+ if d == 'L':
2424+ turns = 4 - turns
2525+2626+ facing = (facing + turns) % 4
2727+2828+ wp = wp.rotate(turns)
2929+3030+ elif d == 'F':
3131+ p1 += DIRS[facing] * n
3232+ p2 += wp * n
3333+3434+print "Part 1:", p1.manhattan
3535+print "Part 2:", p2.manhattan
+69
2020/day13.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1212+from utils import chunks, gcd, lcm, crt, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+# Itertools Functions:
1919+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2020+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2121+# combinations('ABCD', 2) AB AC AD BC BD CD
2222+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2323+2424+tot = 0
2525+res = []
2626+board = {}
2727+table = new_table(None, width=2, height=4)
2828+2929+timestamp = None
3030+buses = []
3131+3232+# Uncomment for multi-group style inputs. :c
3333+# data = ''.join([line for line in fileinput.input()])
3434+# groups = [g.split('\n') for g in data.split('\n\n')]
3535+3636+for y, line in enumerate(fileinput.input()):
3737+ line = line.strip()
3838+ nums = parse_nums(line)
3939+ data = parse_line(r'', line)
4040+4141+ for x, c in enumerate(line):
4242+ board[Point(x, y)] = c
4343+4444+ if y == 0:
4545+ timestamp = int(line)
4646+ else:
4747+ for x in line.split(','):
4848+ if x == 'x':
4949+ buses.append(None)
5050+ else:
5151+ buses.append(int(x))
5252+5353+m = []
5454+x = []
5555+for i, b in enumerate(buses):
5656+ if b:
5757+ m.append(b)
5858+ x.append(i)
5959+6060+print crt(x, m)
6161+6262+6363+6464+num = 1777307553937916
6565+l1 = 2265213528143033
6666+6767+num2 = num - l1
6868+6969+print abs(num2) < abs(num)
+83
2020/day14.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1212+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+# Itertools Functions:
1919+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2020+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2121+# combinations('ABCD', 2) AB AC AD BC BD CD
2222+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2323+2424+tot = 0
2525+res = []
2626+board = {}
2727+table = new_table(None, width=2, height=4)
2828+2929+# Uncomment for multi-group style inputs. :c
3030+# data = ''.join([line for line in fileinput.input()])
3131+# groups = [g.split('\n') for g in data.split('\n\n')]
3232+3333+for y, line in enumerate(fileinput.input()):
3434+ line = line.strip()
3535+ nums = parse_nums(line)
3636+ data = parse_line(r'', line)
3737+3838+ a, b = line.split(' = ')
3939+ # print parts
4040+4141+ if a == 'mask':
4242+ res.append((None, b))
4343+ else:
4444+ res.append((nums[0], nums[1]))
4545+4646+mem = defaultdict()
4747+4848+mask = None
4949+5050+for a, b in res:
5151+ if a is None:
5252+ mask = b
5353+ else:
5454+ addr = a
5555+ write = 0
5656+ floats = [i for i, c in enumerate(reversed(mask)) if c == 'X']
5757+ # print floats
5858+ for i, c in enumerate(reversed(mask)):
5959+ if c == '1':
6060+ write |= (1 << i)
6161+ elif c == '0':
6262+ write |= (addr & (1 << i))
6363+6464+ writes = [write]
6565+ print writes, floats
6666+ for i in floats:
6767+ new_writes = []
6868+ for w in writes:
6969+ print w
7070+ new_writes.append(w & ~(1 << i))
7171+ new_writes.append(w | (1 << i))
7272+7373+ print new_writes
7474+ writes = new_writes
7575+7676+ print writes
7777+7878+7979+ for w in writes:
8080+ mem[w] = b
8181+8282+print sum(mem.values())
8383+
+77
2020/day15.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1212+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+# Itertools Functions:
1919+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2020+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2121+# combinations('ABCD', 2) AB AC AD BC BD CD
2222+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2323+2424+tot = 0
2525+res = []
2626+board = {}
2727+table = new_table(None, width=2, height=4)
2828+2929+# Uncomment for multi-group style inputs. :c
3030+# data = ''.join([line for line in fileinput.input()])
3131+# groups = [g.split('\n') for g in data.split('\n\n')]
3232+3333+for y, line in enumerate(fileinput.input()):
3434+ line = line.strip()
3535+ nums = parse_nums(line)
3636+ data = parse_line(r'', line)
3737+ nums = [int(x) for x in line.split(',')]
3838+3939+ for x, c in enumerate(line):
4040+ board[Point(x, y)] = c
4141+4242+ if y == 0:
4343+ print(data)
4444+4545+print nums
4646+4747+4848+4949+seen = defaultdict(list)
5050+5151+for i, n in enumerate(nums):
5252+ seen[n].append(i)
5353+5454+print seen
5555+5656+spoken = nums[-1]
5757+i = len(nums)
5858+5959+while True:
6060+ # print spoken, i
6161+ if len(seen[spoken]) == 1:
6262+ new_spoke = 0
6363+ else:
6464+ new_spoke = i - seen[spoken][-2] - 1
6565+6666+ # print "newspoke", new_spoke
6767+6868+ spoken = new_spoke
6969+ seen[spoken].append(i)
7070+7171+ i += 1
7272+ if i == 30000000:
7373+ break
7474+7575+print spoken
7676+7777+
+43
2020/day16.py
···11+import fileinput
22+from collections import defaultdict
33+from utils import parse_nums, mul, transposed, resolve_mapping
44+55+fields = {}
66+tickets = []
77+88+for line in fileinput.input():
99+ nums = parse_nums(line)
1010+ if nums:
1111+ if len(nums) == 4:
1212+ fields[line.split(':')[0]] = [abs(x) for x in nums]
1313+ else:
1414+ tickets.append(nums)
1515+1616+valid_tickets = []
1717+part_1 = 0
1818+1919+for ticket in tickets[1:]:
2020+ for n in ticket:
2121+ for a, b, c, d in fields.values():
2222+ if a <= n <= b or c <= n <= d:
2323+ break
2424+2525+ else:
2626+ part_1 += n
2727+ break
2828+ else:
2929+ valid_tickets.append(ticket)
3030+3131+print "Part 1:", part_1
3232+3333+poss = defaultdict(set)
3434+3535+for i, col in enumerate(transposed(valid_tickets)):
3636+ for field, (a, b, c, d) in fields.items():
3737+ if all(a <= n <= b or c <= n <= d for n in col):
3838+ poss[field].add(i)
3939+4040+resolved = resolve_mapping(poss)
4141+4242+departures = [resolved[field] for field in resolved if 'departure' in field]
4343+print "Part 2:", mul(tickets[0][i] for i in departures)
+52
2020/day17.py
···11+import fileinput
22+from collections import defaultdict
33+from itertools import product
44+55+grid_1 = defaultdict(lambda: '.')
66+grid_2 = defaultdict(lambda: '.')
77+88+for y, line in enumerate(fileinput.input()):
99+ for x, c in enumerate(line.strip()):
1010+ grid_1[x, y, 0] = c
1111+ grid_2[x, y, 0, 0] = c
1212+1313+max_dim = max(x, y)
1414+1515+1616+for i in range(6):
1717+ new_grid_1 = defaultdict(lambda: '.')
1818+ new_grid_2 = defaultdict(lambda: '.')
1919+2020+ for x, y, z, w in product(range(-i - 1, max_dim + i + 1), repeat=4):
2121+ neighs_1 = 0
2222+ neighs_2 = 0
2323+2424+ for dx, dy, dz, dw in product(range(-1, 2), repeat=4):
2525+ if dx == 0 and dy == 0 and dz == 0 and dw == 0:
2626+ continue
2727+2828+ if grid_1[x + dx, y + dy, z + dz] == '#' and dw == 0:
2929+ neighs_1 += 1
3030+ if grid_2[x + dx, y + dy, z + dz, w + dw] == '#':
3131+ neighs_2 += 1
3232+3333+3434+ if grid_1[x, y, z] == '#' and (neighs_1 == 2 or neighs_1 == 3):
3535+ new_grid_1[x, y, z] = '#'
3636+3737+ elif grid_1[x, y, z] == '.' and neighs_1 == 3:
3838+ new_grid_1[x, y, z] = '#'
3939+4040+ if grid_2[x, y, z, w] == '#' and (neighs_2 == 2 or neighs_2 == 3):
4141+ new_grid_2[x, y, z, w] = '#'
4242+4343+ elif grid_2[x, y, z, w] == '.' and neighs_2 == 3:
4444+ new_grid_2[x, y, z, w] = '#'
4545+4646+ grid_1 = new_grid_1
4747+ grid_2 = new_grid_2
4848+4949+5050+print "Part 1:", sum(c == '#' for c in grid_1.values())
5151+print "Part 2:", sum(c == '#' for c in grid_2.values())
5252+
+107
2020/day18.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1212+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+# Itertools Functions:
1919+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2020+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2121+# combinations('ABCD', 2) AB AC AD BC BD CD
2222+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2323+2424+tot = 0
2525+res = []
2626+board = {}
2727+table = new_table(None, width=2, height=4)
2828+2929+# Uncomment for multi-group style inputs. :c
3030+# data = ''.join([line for line in fileinput.input()])
3131+# groups = [g.split('\n') for g in data.split('\n\n')]
3232+3333+for y, line in enumerate(fileinput.input()):
3434+ line = line.strip().replace(' ', '')
3535+ nums = parse_nums(line)
3636+ data = parse_line(r'', line)
3737+3838+ for x, c in enumerate(line):
3939+ board[Point(x, y)] = c
4040+4141+ if y == 0:
4242+ print(data)
4343+4444+ res.append(line)
4545+4646+print tot
4747+4848+NUMS = '0123456789'
4949+5050+5151+def is_number(str):
5252+ try:
5353+ int(str)
5454+ return True
5555+ except ValueError:
5656+ return False
5757+5858+def is_name(str):
5959+ return re.match("\w+", str)
6060+6161+def peek(stack):
6262+ return stack[-1] if stack else None
6363+6464+def apply_operator(operators, values):
6565+ operator = operators.pop()
6666+ right = values.pop()
6767+ left = values.pop()
6868+ values.append(eval("{0}{1}{2}".format(left, operator, right)))
6969+7070+def greater_precedence(op1, op2):
7171+ precedences = {'+' : 1, '-' : 0, '*' : 0, '/' : 0}
7272+ return precedences[op1] > precedences[op2]
7373+7474+def evaluate(expression):
7575+ tokens = re.findall("[+/*()-]|\d+", expression)
7676+ values = []
7777+ operators = []
7878+ for token in tokens:
7979+ if is_number(token):
8080+ values.append(int(token))
8181+ elif token == '(':
8282+ operators.append(token)
8383+ elif token == ')':
8484+ top = peek(operators)
8585+ while top is not None and top != '(':
8686+ apply_operator(operators, values)
8787+ top = peek(operators)
8888+ operators.pop() # Discard the '('
8989+ else:
9090+ # Operator
9191+ top = peek(operators)
9292+ while top is not None and top not in "()" and greater_precedence(top, token):
9393+ apply_operator(operators, values)
9494+ top = peek(operators)
9595+ operators.append(token)
9696+ while peek(operators) is not None:
9797+ apply_operator(operators, values)
9898+9999+ return values[0]
100100+101101+102102+for line in res:
103103+ a = evaluate(line)
104104+ print a, '=', line
105105+ tot += a
106106+107107+print tot
+71
2020/day19.py
···11+import fileinput
22+from collections import defaultdict
33+from utils import parse_nums, memoize
44+55+@memoize
66+def resolve(r):
77+ if type(rules[r]) == str:
88+ return [rules[r]]
99+1010+ matches = []
1111+ for subrule in rules[r]:
1212+ submatches = ['']
1313+ for n in subrule:
1414+ new = []
1515+ for m in resolve(n):
1616+ for existing in submatches:
1717+ new.append(existing + m)
1818+1919+ submatches = new
2020+2121+ matches.extend(submatches)
2222+2323+ return matches
2424+2525+rules = defaultdict(list)
2626+messages = []
2727+2828+for line in fileinput.input():
2929+ line = line.strip()
3030+ nums = parse_nums(line)
3131+3232+ if nums:
3333+ parts = line.split(": ")[1].split(" | ")
3434+ r = nums[0]
3535+ for p in parts:
3636+ if '"' in p:
3737+ rules[r] = p[1]
3838+ else:
3939+ rules[r].append([int(x) for x in p.split(' ')])
4040+4141+ elif line:
4242+ messages.append(line)
4343+4444+4545+pl = len(resolve(42)[0])
4646+4747+part_1 = 0
4848+part_2 = 0
4949+5050+for line in messages:
5151+ if line in resolve(0):
5252+ part_1 += 1
5353+5454+ orig_line = line
5555+ a = 0
5656+ b = 0
5757+5858+ while line[:pl] in resolve(42):
5959+ line = line[pl:]
6060+ a += 1
6161+6262+ while line[:pl] in resolve(31):
6363+ line = line[pl:]
6464+ b += 1
6565+6666+ if a > b and b > 0 and not line:
6767+ print orig_line
6868+ part_2 += 1
6969+7070+print "Part 1:", part_1
7171+print "Part 2:", part_2
+329
2020/day20-solve.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from collections import Counter, defaultdict, deque, namedtuple # NOQA
88+99+from utils import parse_nums, mul, print_grid, transposed, rotated, Point, new_table
1010+1111+tot = 0
1212+res = []
1313+board = new_table('.', width=10, height=10)
1414+1515+tiles = {}
1616+curr = 0
1717+sy = 0
1818+1919+MONSTER = """
2020+ #
2121+# ## ## ###
2222+ # # # # # #
2323+"""
2424+2525+for y, line in enumerate(fileinput.input()):
2626+ line = line.strip()
2727+ nums = parse_nums(line)
2828+2929+ if not line:
3030+ tiles[curr] = copy.deepcopy(board)
3131+ board = new_table('.', width=10, height=10)
3232+3333+ if nums:
3434+ curr = nums[0]
3535+ sy = y
3636+ continue
3737+3838+ for x, c in enumerate(line):
3939+ board[y - sy - 1][x] = c
4040+4141+tiles[curr] = copy.deepcopy(board)
4242+4343+edges = Counter()
4444+piece_edges = defaultdict(set)
4545+4646+# find edges:
4747+for id, t in tiles.items():
4848+ x = copy.deepcopy(t)
4949+ for _ in range(4):
5050+ row = ''.join(x[0])
5151+ edges[row] += 1
5252+ edges[row[::-1]] += 1
5353+ piece_edges[row].add(id)
5454+ piece_edges[row[::-1]].add(id)
5555+ x = rotated(x)
5656+5757+# for c in edges.most_common():
5858+# print c
5959+6060+cands = []
6161+6262+for id, tile in tiles.items():
6363+ ones = 0
6464+ for _ in range(4):
6565+ row = ''.join(x[0])
6666+ if edges.get(row) == 1:
6767+ ones += 1
6868+ if edges.get(row[::-1]) == 1:
6969+ ones += 1
7070+ x = rotated(x)
7171+7272+ if ones >= 1:
7373+ cands.append(id)
7474+7575+ # print ones
7676+7777+graph = defaultdict(set)
7878+7979+for p, e in piece_edges.items():
8080+ # print p, e
8181+ for x in e:
8282+ for y in e:
8383+ graph[x].add(y)
8484+ graph[y].add(x)
8585+8686+8787+tot = 1
8888+corners = []
8989+for k, v in graph.items():
9090+ # print k, v
9191+ if len(v) == 3:
9292+ corners.append(k)
9393+ # print k
9494+ tot *= k
9595+9696+# print tot
9797+9898+puzzle = {}
9999+x = 0
100100+y = 0
101101+102102+start = corners[0]
103103+puzzle[0, 0] = start
104104+o = [i for i in graph[start] if i != start]
105105+puzzle[0, 1] = o[0]
106106+puzzle[1, 0] = o[1]
107107+108108+109109+110110+m = int(math.sqrt(len(tiles)))
111111+x = 2
112112+y = 2
113113+114114+while x < m:
115115+ for k, v in graph.items():
116116+ if x == m - 1:
117117+ if len(v) == 3 and k in graph[puzzle[x - 1, 0]] and k not in puzzle.values():
118118+ puzzle[x, 0] = k
119119+ x += 1
120120+ break
121121+ else:
122122+ if len(v) == 4 and k in graph[puzzle[x - 1, 0]] and k not in puzzle.values():
123123+ puzzle[x, 0] = k
124124+ x += 1
125125+ break
126126+127127+while y < m:
128128+ for k, v in graph.items():
129129+ if y == m - 1:
130130+ if len(v) == 3 and k in graph[puzzle[0, y - 1]] and k not in puzzle.values():
131131+ puzzle[0, y] = k
132132+ y += 1
133133+ break
134134+ else:
135135+ if len(v) == 4 and k in graph[puzzle[0, y - 1]] and k not in puzzle.values():
136136+ puzzle[0, y] = k
137137+ y += 1
138138+ break
139139+140140+# print puzzle
141141+142142+143143+for y in range(1, m):
144144+ for x in range(1, m):
145145+ # print x, y
146146+ for k, v in graph.items():
147147+ # print k, v, puzzle.get((x, y - 1)), puzzle.get((x - 1, y))
148148+ if puzzle.get((x, y - 1)) in v and puzzle.get((x - 1, y)) in v and k not in puzzle.values():
149149+ puzzle[x, y] = k
150150+151151+assert len(puzzle) == len(tiles)
152152+153153+print "Piece arrangement:"
154154+for y in range(m):
155155+ print ' '.join(str(puzzle[x, y]) for x in range(m))
156156+print
157157+158158+PUZZLE = {}
159159+160160+for y in range(m):
161161+ for x in range(m):
162162+ PUZZLE[x, y] = tiles[puzzle[x, y]]
163163+164164+row = ''.join(PUZZLE[0, 0][-1])
165165+col = ''.join(transposed(PUZZLE[0, 0])[-1])
166166+167167+# print row, piece_edges[row]
168168+# print col, piece_edges[col]
169169+170170+for _ in range(2):
171171+ for _ in range(4):
172172+ row = ''.join(PUZZLE[0, 0][-1])
173173+ col = ''.join(transposed(PUZZLE[0, 0])[-1])
174174+ # print_grid(PUZZLE[0, 0])
175175+ # print
176176+ if puzzle[0, 1] in piece_edges[row] and puzzle[1, 0] in piece_edges[col]:
177177+ # print "good"
178178+ break
179179+180180+ PUZZLE[0, 0] = rotated(PUZZLE[0, 0])
181181+182182+ else:
183183+ PUZZLE[0, 0] = transposed(PUZZLE[0, 0])
184184+ continue
185185+186186+ break
187187+188188+# print puzzle[0, 0], puzzle[0, 1], puzzle[1, 0]
189189+# print_grid(PUZZLE[0, 0])
190190+191191+# x, 0
192192+for x in range(1, m):
193193+ for _ in range(2):
194194+ for _ in range(4):
195195+ row = ''.join(transposed(PUZZLE[x, 0])[0])
196196+ # print "trying", row
197197+ if row == ''.join(transposed(PUZZLE[x - 1, 0])[-1]):
198198+ # print "good for x=", x
199199+ # print_grid(PUZZLE[x, 0])
200200+ break
201201+202202+ PUZZLE[x, 0] = rotated(PUZZLE[x, 0])
203203+204204+ else:
205205+ PUZZLE[x, 0] = transposed(PUZZLE[x, 0])
206206+ continue
207207+208208+ break
209209+210210+ else:
211211+ print "no match for", x
212212+213213+# y, 0
214214+for y in range(1, m):
215215+ for _ in range(2):
216216+ for _ in range(4):
217217+ row = ''.join(PUZZLE[0, y][0])
218218+ # print "trying", row
219219+ if row == ''.join(PUZZLE[0, y - 1][-1]):
220220+ # print "good for y=", y
221221+ # print_grid(PUZZLE[0, y])
222222+ break
223223+224224+ PUZZLE[0, y] = rotated(PUZZLE[0, y])
225225+226226+ else:
227227+ PUZZLE[0, y] = transposed(PUZZLE[0, y])
228228+ continue
229229+230230+ break
231231+232232+ else:
233233+ print "no match for", y
234234+235235+236236+for y in range(1, m):
237237+ for x in range(1, m):
238238+ for _ in range(2):
239239+ for _ in range(4):
240240+ col = ''.join(transposed(PUZZLE[x, y])[0])
241241+ row = ''.join(PUZZLE[x, y][0])
242242+243243+ ocol = ''.join(transposed(PUZZLE[x - 1, y])[-1])
244244+ orow = ''.join(PUZZLE[x, y - 1][-1])
245245+ if row == orow and col == ocol:
246246+ # print "good for {}, {}".format(x, y)
247247+ # print_grid(PUZZLE[x, y])
248248+ break
249249+250250+ PUZZLE[x, y] = rotated(PUZZLE[x, y])
251251+252252+ else:
253253+ PUZZLE[x, y] = transposed(PUZZLE[x, y])
254254+ continue
255255+256256+ break
257257+258258+ else:
259259+ print "no match for", x, y
260260+261261+monster = [[None for _ in range(8 * m)] for _ in range(8 * m)]
262262+263263+for y in range(m):
264264+ for x in range(m):
265265+ for j in range(8):
266266+ for i in range(8):
267267+ # print x, y, i, j
268268+ monster[y*8 + j][x*8 + i] = PUZZLE[x, y][j + 1][i + 1]
269269+270270+271271+MONSTER = []
272272+MONSTER.append(" # ")
273273+MONSTER.append("# ## ## ###")
274274+MONSTER.append(" # # # # # # ")
275275+276276+minds = []
277277+278278+mx = 20
279279+my = 3
280280+for y in range(len(MONSTER)):
281281+ for x, c in enumerate(MONSTER[y]):
282282+ if c == '#':
283283+ minds.append((x, y))
284284+285285+# print minds
286286+287287+for _ in range(2):
288288+ monster = rotated(monster)
289289+290290+# print_grid(monster)
291291+292292+for _ in range(2):
293293+ for _ in range(4):
294294+ m_count = 0
295295+ occupied = set()
296296+ for y in range(len(monster) - my):
297297+ for x in range(len(monster[0]) - mx):
298298+ if all(monster[y+j][x+i] == '#' for i, j in minds):
299299+ # print "monster!"
300300+ m_count += 1
301301+302302+ for i, j in minds:
303303+ occupied.add((x+i, y+j))
304304+305305+ if m_count > 1:
306306+ # print m_count, occupied
307307+ final_monster = {}
308308+ tot = 0
309309+ for y in range(m * 8):
310310+ for x in range(m * 8):
311311+ p = Point(x, y)
312312+ if monster[y][x] == '#':
313313+ if (x, y) not in occupied:
314314+ tot += 1
315315+ final_monster[p] = '~'
316316+ else:
317317+ final_monster[p] = 'O'
318318+319319+ else:
320320+ final_monster[p] = ' '
321321+322322+ print_grid(final_monster)
323323+ print
324324+ print "Water roughness:", tot
325325+326326+ monster = rotated(monster)
327327+328328+ monster = transposed(monster)
329329+
+173
2020/day20.py
···11+import math
22+import fileinput
33+from itertools import permutations
44+from collections import Counter, defaultdict
55+from utils import mul, print_grid, transposed, rotated
66+77+88+TILES = defaultdict(list)
99+1010+# Read problem input
1111+for line in fileinput.input():
1212+ if line.startswith('Tile'):
1313+ id = int(line.replace(':', '').split(' ')[1])
1414+ elif len(line) > 5:
1515+ TILES[id].append(line.strip())
1616+1717+1818+# Map edges to pieces that share that edge
1919+piece_edges = defaultdict(set)
2020+2121+for id, tile in TILES.items():
2222+ for _ in range(4):
2323+ row = ''.join(tile[0])
2424+ piece_edges[row].add(id)
2525+ piece_edges[row[::-1]].add(id)
2626+ tile = rotated(tile)
2727+2828+2929+# Construct graph of neighbouring pieces
3030+graph = defaultdict(set)
3131+3232+for neighbouring_pieces in piece_edges.values():
3333+ for x, y in permutations(neighbouring_pieces, 2):
3434+ graph[x].add(y)
3535+3636+3737+# Corners are the only pieces that neighbour exactly 2 other tiles.
3838+corners = [tile for tile, neighs in graph.items() if len(neighs) == 2]
3939+print "Corner ID product:", mul(corners)
4040+print
4141+4242+4343+# Build up the full arrangement of the tiles. Arbitrarily pick
4444+# a corner to be the top-left, and assign its neighbours.
4545+puzzle = {}
4646+puzzle[0, 0] = corners[0]
4747+4848+4949+# Let `m` represent the m by m arrangement of tiles.
5050+m = int(math.sqrt(len(TILES)))
5151+5252+5353+# Assign tiles for the top row and left column by choosing from the subset
5454+# of tiles that contain either 2 or 3 neighbours (ie. edges and corners).
5555+# Make sure to not reuse tiles since that ruins this algorithm.
5656+edges = [tile for tile, neighs in graph.items() if len(neighs) == 3]
5757+5858+for x in range(1, m):
5959+ for tile in edges + corners:
6060+ if tile in graph[puzzle[x - 1, 0]] and tile not in puzzle.values():
6161+ puzzle[x, 0] = tile
6262+6363+for y in range(1, m):
6464+ for tile in edges + corners:
6565+ if tile in graph[puzzle[0, y - 1]] and tile not in puzzle.values():
6666+ puzzle[0, y] = tile
6767+6868+6969+# Now assign the remaining tiles. Since the previous tiles were already filled
7070+# in, we can check the left and top neighbour for every candidate location,
7171+# which will uniquely match a single tile's neighbour list.
7272+for y in range(1, m):
7373+ for x in range(1, m):
7474+ for tile, neighbours in graph.items():
7575+ left = puzzle[x - 1, y]
7676+ top = puzzle[x, y - 1]
7777+7878+ if left in neighbours and top in neighbours and tile not in puzzle.values():
7979+ puzzle[x, y] = tile
8080+8181+print "Resolved piece arrangement:"
8282+for y in range(m):
8383+ print ' '.join(str(puzzle[x, y]) for x in range(m))
8484+print
8585+8686+8787+# Now we need to actually orient all the pieces. Let `PUZZLE` be a dictionary
8888+# that maps tile locations to 2D arrays representing the pieces themselves.
8989+PUZZLE = {pos: TILES[id] for pos, id in puzzle.items()}
9090+9191+9292+# Define some helper functions for manipulating tiles.
9393+def orientations(tile):
9494+ """Generates all 8 orientations of a 2D array."""
9595+ for _ in range(2):
9696+ for _ in range(4):
9797+ yield tile
9898+ tile = rotated(tile)
9999+100100+ tile = transposed(tile)
101101+102102+def aligned(a, b):
103103+ """Returns if the final row or col of `a` matches the first row or col of `b`"""
104104+ return (
105105+ ''.join(a[-1]) == ''.join(b[0]) or
106106+ ''.join(transposed(a)[-1]) == ''.join(transposed(b)[0])
107107+ )
108108+109109+110110+# We need to get the top-left corner into an orientation where its bottom and
111111+# right edges match the neighbours based on the arrangement we determined above.
112112+for tile in orientations(PUZZLE[0, 0]):
113113+ bottom = ''.join(tile[-1])
114114+ right = ''.join(transposed(tile)[-1])
115115+116116+ if puzzle[0, 1] in piece_edges[bottom] and puzzle[1, 0] in piece_edges[right]:
117117+ PUZZLE[0, 0] = tile
118118+ break
119119+120120+121121+# Align remaining tiles by matching them up via their top and left edges, if present.
122122+for y in range(m):
123123+ for x in range(m):
124124+ if x == 0 and y == 0:
125125+ continue
126126+127127+ for tile in orientations(PUZZLE[x, y]):
128128+ if (x == 0 or aligned(PUZZLE[x - 1, y], tile)) and (y == 0 or aligned(PUZZLE[x, y - 1], tile)):
129129+ PUZZLE[x, y] = tile
130130+ break
131131+132132+133133+# Process the puzzle to form the image (removing borders of tiles).
134134+# Store as 2D array instead of dictionary so we can re-orient it.
135135+IMG = [[None for _ in range(8 * m)] for _ in range(8 * m)]
136136+for (x, y), tile in PUZZLE.items():
137137+ for j in range(8):
138138+ for i in range(8):
139139+ IMG[y*8 + j][x*8 + i] = tile[j + 1][i + 1]
140140+141141+142142+# Get the offset positions for the sea monster components.
143143+MONSTER = """\
144144+ #
145145+# ## ## ###
146146+ # # # # # #
147147+"""
148148+149149+m_pos = []
150150+for y, row in enumerate(MONSTER.split('\n')):
151151+ for x, c in enumerate(row):
152152+ if c == '#':
153153+ m_pos.append((x, y))
154154+155155+156156+# Search for sea monsters!
157157+for image in orientations(IMG):
158158+ occupied = set()
159159+ for y in range(m * 8 - max(y for x, y in m_pos)):
160160+ for x in range(m * 8 - max(x for x, y in m_pos)):
161161+ if all(image[y+j][x+i] == '#' for i, j in m_pos):
162162+ for i, j in m_pos:
163163+ occupied.add((x+i, y+j))
164164+165165+ if occupied:
166166+ final_img = {}
167167+ for y in range(m * 8):
168168+ for x in range(m * 8):
169169+ if image[y][x] == '#':
170170+ final_img[x, y] = 'O' if (x, y) in occupied else '~'
171171+172172+ print_grid(final_img)
173173+ print "\nWater roughness:", sum(1 for c in final_img.values() if c == '~')
+62
2020/day21.py
···11+import fileinput
22+from utils import resolve_mapping
33+from collections import Counter, defaultdict, deque, namedtuple # NOQA
44+55+foods = []
66+77+for y, line in enumerate(fileinput.input()):
88+ line = line.strip()
99+1010+ ingredients, allergens = line.split(' (')
1111+ allergens = allergens.replace(')', '').replace(',', '').split(' ')[1:]
1212+1313+ foods.append((ingredients.split(' '), allergens))
1414+1515+ing_to_all = defaultdict(set)
1616+all_to_ing = defaultdict(set)
1717+1818+for ingredients, allergens in foods:
1919+ for i in ingredients:
2020+ for a in allergens:
2121+ ing_to_all[i].add(a)
2222+ all_to_ing[a].add(i)
2323+2424+candidates = defaultdict(set)
2525+resolved_ing = set()
2626+2727+for a in all_to_ing:
2828+ poss = []
2929+ for ingredients, allergens in foods:
3030+ if a in allergens:
3131+ poss.append(ingredients)
3232+3333+ if poss:
3434+ resolve = set(poss[0])
3535+ for p in poss[1:]:
3636+ resolve &= set(p)
3737+3838+ for r in resolve:
3939+ candidates[a].add(r)
4040+ resolved_ing.add(r)
4141+ else:
4242+ print "empty poss", a
4343+4444+good = set(ing_to_all) - resolved_ing
4545+print len(good)
4646+4747+tot = 0
4848+for ingredients, allergens in foods:
4949+ for i in ingredients:
5050+ if i in good:
5151+ tot += 1
5252+5353+5454+print tot
5555+5656+resolved = resolve_mapping(candidates)
5757+5858+l = []
5959+for r in sorted(resolved):
6060+ l.append(resolved[r])
6161+6262+print ','.join(l)
+92
2020/day22.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import copy # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA
1212+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+tot = 0
1919+res = []
2020+board = {}
2121+table = new_table(None, width=2, height=4)
2222+2323+p1 = deque()
2424+p2 = deque()
2525+2626+is1 = True
2727+2828+for y, line in enumerate(fileinput.input()):
2929+ line = line.strip()
3030+ nums = parse_nums(line)
3131+ data = parse_line(r'', line)
3232+3333+ if ':' in line:
3434+ continue
3535+3636+ if not line:
3737+ is1 = False
3838+3939+ if nums:
4040+ if is1:
4141+ p1.append(nums[0])
4242+ else:
4343+ p2.append(nums[0])
4444+4545+4646+def serialize(p1, p2):
4747+ a = ','.join(str(x) for x in p1)
4848+ b = ','.join(str(x) for x in p2)
4949+ return a + '~' + b
5050+5151+SEEN = set()
5252+5353+depth = 0
5454+5555+@memoize
5656+def combat(p1, p2):
5757+ # print "triggered new combat", p1, p2
5858+ while p1 and p2:
5959+ state = serialize(p1, p2)
6060+ if state in SEEN:
6161+ return p1, True
6262+6363+ SEEN.add(state)
6464+6565+ a = p1.popleft()
6666+ b = p2.popleft()
6767+6868+ if len(p1) >= a and len(p2) >= b:
6969+ # print "triggering combat for", a, b
7070+ p1_win = combat(deque(list(p1)[:a]), deque(list(p2)[:b]))[1]
7171+ else:
7272+ p1_win = a > b
7373+7474+ if p1_win:
7575+ p1.append(a)
7676+ p1.append(b)
7777+ else:
7878+ p2.append(b)
7979+ p2.append(a)
8080+8181+ return p1 if p1 else p2, True if p1 else False
8282+8383+8484+deck, winner = combat(p1, p2)
8585+print deck, winner
8686+8787+8888+ans = 0
8989+for i, a in enumerate(reversed(deck), start = 1):
9090+ ans += i * a
9191+9292+print ans
+68
2020/day23.py
···11+NUM_CUPS = 1000000
22+ITERS = 10000000
33+44+class Cup:
55+ def __init__(self, n):
66+ self.n = n
77+88+ def __repr__(self):
99+ return "{} -> ({}) -> {}".format(self.prev.n, self.n, self.next.n)
1010+1111+labeling = [int(x) for x in raw_input()]
1212+cup_ptrs = {}
1313+1414+for i in range(1, NUM_CUPS + 1):
1515+ cup_ptrs[i] = Cup(i)
1616+1717+curr_cup = None
1818+prev = None
1919+for n in labeling:
2020+ if curr_cup is None:
2121+ curr_cup = cup_ptrs[n]
2222+ prev = curr_cup
2323+ else:
2424+ cup_ptrs[n].prev = prev
2525+ prev.next = cup_ptrs[n]
2626+ prev = cup_ptrs[n]
2727+2828+for n in range(len(labeling) + 1, NUM_CUPS + 1):
2929+ cup_ptrs[n].prev = prev
3030+ prev.next = cup_ptrs[n]
3131+ prev = cup_ptrs[n]
3232+3333+curr_cup.prev = prev
3434+prev.next = curr_cup
3535+3636+for move in range(ITERS):
3737+ curr = curr_cup.n
3838+3939+ # Pick up three cups
4040+ a = curr_cup.next
4141+ b = a.next
4242+ c = b.next
4343+4444+ # Cup spacing adjusted
4545+ a.prev.next = c.next
4646+ c.next.prev = a.prev
4747+4848+ dval = curr - 1
4949+ if dval == 0:
5050+ dval = NUM_CUPS
5151+ while dval == a.n or dval == b.n or dval == c.n:
5252+ dval -= 1
5353+ if dval == 0:
5454+ dval = NUM_CUPS
5555+5656+ dest_cup = cup_ptrs[dval]
5757+ tmp_cup = dest_cup.next
5858+5959+ dest_cup.next = a
6060+ a.prev = dest_cup
6161+6262+ c.next = tmp_cup
6363+ tmp_cup.prev = c
6464+6565+ # Move current cup to the next cup.
6666+ curr_cup = curr_cup.next
6767+6868+print cup_ptrs[1].next.n * cup_ptrs[1].next.next.n
+62
2020/day24.py
···11+import fileinput
22+from collections import defaultdict
33+44+HEX_DIRS = {
55+ 'e': (1, -1, 0),
66+ 'ne': (1, 0, -1),
77+ 'se': (0, -1, 1),
88+ 'w': (-1, 1, 0),
99+ 'sw': (-1, 0, 1),
1010+ 'nw': (0, 1, -1),
1111+}
1212+1313+def hex_neighbours(x, y, z):
1414+ for dx, dy, dz in HEX_DIRS.values():
1515+ yield x + dx, y + dy, z + dz
1616+1717+1818+TILES = defaultdict(bool)
1919+2020+for line in fileinput.input():
2121+ x = 0
2222+ y = 0
2323+ z = 0
2424+ curr = ''
2525+2626+ for c in line:
2727+ curr += c
2828+ if curr in HEX_DIRS:
2929+ dx, dy, dz = HEX_DIRS[curr]
3030+ x += dx
3131+ y += dy
3232+ z += dz
3333+ curr = ''
3434+3535+ TILES[x, y, z] = not TILES[x, y, z]
3636+3737+print "Part 1:", sum(TILES.values())
3838+3939+for day in range(1, 100 + 1):
4040+ new_tiles = defaultdict(bool)
4141+4242+ for old_pos in TILES.keys():
4343+ for pos in hex_neighbours(*old_pos):
4444+ neighs = 0
4545+ for n in hex_neighbours(*pos):
4646+ if TILES[n]:
4747+ neighs += 1
4848+4949+ if TILES[pos] and (neighs == 0 or neighs > 2):
5050+ pass
5151+ elif not TILES[pos] and neighs == 2:
5252+ new_tiles[pos] = True
5353+ else:
5454+ if TILES[pos]:
5555+ new_tiles[pos] = True
5656+5757+ TILES = new_tiles
5858+5959+print "Part 2:", sum(TILES.values())
6060+6161+6262+
+19
2020/day25.py
···11+import sys
22+import fileinput
33+44+55+MOD = 20201227
66+KEYS = [int(n) for n in fileinput.input()]
77+88+card_pub = KEYS[0]
99+door_pub = KEYS[1]
1010+1111+1212+for loop_size in range(1, MOD + 1):
1313+ val = pow(7, loop_size, MOD)
1414+ if val == card_pub:
1515+ print pow(door_pub, loop_size, MOD)
1616+ break
1717+ elif val == door_pub:
1818+ print pow(card_pub, loop_size, MOD)
1919+ break
+51
2020/search.py
···11+def bfs(start, graph):
22+ from collections import deque
33+44+ max_depth = 0
55+ depths = {}
66+ horizon = deque([(start, 0)]) # node, depth
77+ seen = {start: None}
88+99+ # TODO
1010+ def is_goal(node):
1111+ return graph.get(node) == 'G'
1212+1313+ def gen_neighbours(node):
1414+ for n in node.neighbours_4():
1515+ if graph.get(n, ' ') != ' ':
1616+ yield n
1717+1818+1919+ while horizon:
2020+ node, depth = horizon.popleft() # pop() for DFS
2121+ depths[node] = depth
2222+2323+ if depth > max_depth:
2424+ max_depth = depth
2525+ print "BFS @ {}; depth={}, horizon={}, seen={}".format(node, depth, len(horizon), len(seen))
2626+2727+ for new in gen_neighbours(node):
2828+ if new in seen:
2929+ continue
3030+3131+ seen[new] = node
3232+ horizon.append((new, depth + 1))
3333+3434+ if is_goal(new):
3535+ print "FOUND GOAL", new, depth + 1
3636+ path = []
3737+ curr = new
3838+ while curr is not None:
3939+ path.append(curr)
4040+ curr = seen[curr]
4141+ for p in reversed(path):
4242+ print p
4343+ pass
4444+ print "FOUND GOAL", new, depth + 1
4545+ return depth + 1
4646+4747+ print "FLOOD FILL COMPLETE, max_depth={}, seen={}".format(max_depth, len(seen))
4848+ return depths
4949+5050+start = Point(0, 0)
5151+bfs(start, board)
+16-6
2020/starter.py
···22import sys # NOQA
33import re # NOQA
44import math # NOQA
55+import copy # NOQA
56import fileinput
67from string import ascii_uppercase, ascii_lowercase # NOQA
78from collections import Counter, defaultdict, deque, namedtuple # NOQA
89from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
9101010-from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA
1112from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1213from utils import new_table, transposed, rotated # NOQA
1314from utils import md5, sha256, knot_hash # NOQA
1415from utils import VOWELS, CONSONANTS # NOQA
1515-from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
16171718# Itertools Functions:
1819# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
···2021# combinations('ABCD', 2) AB AC AD BC BD CD
2122# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
22232323-total = 0
2424-result = []
2424+tot = 0
2525+res = []
2626+board = {}
2527table = new_table(None, width=2, height=4)
26282727-for i, line in enumerate(fileinput.input()):
2929+# Uncomment for multi-group style inputs. :c
3030+# data = ''.join([line for line in fileinput.input()])
3131+# groups = [g.split('\n') for g in data.split('\n\n')]
3232+3333+for y, line in enumerate(fileinput.input()):
2834 line = line.strip()
2935 nums = parse_nums(line)
3036 data = parse_line(r'', line)
31373232- if i == 0:
3838+ for x, c in enumerate(line):
3939+ board[Point(x, y)] = c
4040+4141+ if y == 0:
3342 print(data)
4343+
+147-7
2020/utils.py
···22import math
33import hashlib
44import operator
55+import copy
66+from collections import Counter
57from functools import total_ordering
6879···7678 return a * b / gcd(a, b)
777978808181+def egcd(a, b):
8282+ x0, x1, y0, y1 = 1, 0, 0, 1
8383+ while b:
8484+ q, a, b = a // b, b, a % b
8585+ x0, x1 = x1, x0 - q * x1
8686+ y0, y1 = y1, y0 - q * y1
8787+ return a, x0, y0
8888+8989+def modinv(a, n):
9090+ g, x, _ = egcd(a, n)
9191+ if g == 1:
9292+ return x % n
9393+ else:
9494+ raise ValueError("%d is not invertible mod %d" % (a, n))
9595+9696+def crt(rems, mods):
9797+ ''' Solve a system of modular equivalences via the Chinese Remainder Theorem.
9898+ Does not require pairwise coprime moduli. '''
9999+100100+ # copy inputs
101101+ orems, omods = rems, mods
102102+ rems = list(rems)
103103+ mods = list(mods)
104104+105105+ newrems = []
106106+ newmods = []
107107+108108+ for i in range(len(mods)):
109109+ for j in range(i+1, len(mods)):
110110+ g = gcd(mods[i], mods[j])
111111+ if g == 1:
112112+ continue
113113+ if rems[i] % g != rems[j] % g:
114114+ raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g))
115115+ mods[j] //= g
116116+117117+ while 1:
118118+ # transfer any remaining gcds to mods[j]
119119+ g = gcd(mods[i], mods[j])
120120+ if g == 1:
121121+ break
122122+ mods[i] //= g
123123+ mods[j] *= g
124124+125125+ if mods[i] == 1:
126126+ continue
127127+128128+ newrems.append(rems[i] % mods[i])
129129+ newmods.append(mods[i])
130130+131131+ rems, mods = newrems, newmods
132132+133133+ # standard CRT
134134+ s = 0
135135+ n = 1
136136+ for k in mods:
137137+ n *= k
138138+139139+ for i in range(len(mods)):
140140+ ni = n // mods[i]
141141+ s += rems[i] * modinv(ni, mods[i]) * ni
142142+ return s % n, n
143143+144144+79145def min_max_xy(points):
80146 if len(points) == 0:
81147 return None, None, None, None
···93159 return min_x, max_x, min_y, max_y
94160951619696-def print_grid(grid, f=None):
162162+def print_grid(grid, f=None, quiet=False):
97163 if f is None:
98164 f = lambda x: x # NOQA
99165166166+ counts = Counter()
167167+ serialized = []
168168+100169 if type(grid) is dict:
101170 positions = grid.keys()
102171 min_x, max_x, min_y, max_y = min_max_xy(positions)
103172 if type(positions[0]) is tuple:
104173 for y in range(min_y, max_y + 1):
105105- print ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1))
174174+ row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1))
175175+ if not quiet:
176176+ print row
177177+ serialized.append(row)
178178+ for c in row:
179179+ counts[c] += 1
180180+106181 else:
107182 # (x, y) => point
108183 for y in range(min_y, max_y + 1):
109109- print ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1))
184184+ row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1))
185185+ if not quiet:
186186+ print row
187187+ serialized.append(row)
188188+ for c in row:
189189+ counts[c] += 1
110190 else:
111191 for y in range(len(grid)):
112112- print ''.join(f(grid[y][x]) for x in range(len(grid[0])))
192192+ row = ''.join(f(grid[y][x]) for x in range(len(grid[0])))
193193+ if not quiet:
194194+ print row
195195+ serialized.append(row)
196196+ for c in row:
197197+ counts[c] += 1
198198+199199+ # if not quiet:
200200+ # print "height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y)
201201+ # print "width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x)
202202+ # print "Statistics:"
203203+ # for item, num in counts.most_common():
204204+ # print "{}: {}".format(item, num)
205205+206206+ return serialized
207207+208208+def resolve_mapping(candidates):
209209+ resolved = {}
210210+211211+ # Ensure the mapping is key -> set(values).
212212+ candidates_map = {}
213213+ for k, v in candidates.items():
214214+ candidates_map[k] = set(v)
215215+216216+ while len(resolved) < len(candidates_map):
217217+ for candidate in candidates_map:
218218+ if len(candidates_map[candidate]) == 1 and candidate not in resolved:
219219+ r = candidates_map[candidate].pop()
220220+ for c in candidates_map:
221221+ candidates_map[c].discard(r)
222222+223223+ resolved[candidate] = r
224224+ break
225225+226226+ return resolved
113227114228115229def memoize(f):
···117231 cache = {}
118232119233 def _mem_fn(*args):
120120- if args not in cache:
121121- cache[args] = f(*args)
122122- return cache[args]
234234+ hargs = (','.join(str(x) for x in args))
235235+ if hargs not in cache:
236236+ cache[hargs] = f(*args)
237237+ return cache[hargs]
123238124239 _mem_fn.cache = cache
125240 return _mem_fn
···253368 return math.atan2(self.y, self.x)
254369 return math.atan2(self.y - to.y, self.x - to.x)
255370371371+ def rotate(self, turns):
372372+ """Returns the rotation of the Point around (0, 0) `turn` times clockwise."""
373373+ turns = turns % 4
374374+375375+ if turns == 1:
376376+ return Point(self.y, -self.x)
377377+ elif turns == 2:
378378+ return Point(-self.x, -self.y)
379379+ elif turns == 3:
380380+ return Point(-self.y, self.x)
381381+ else:
382382+ return self
383383+256384 @property
257385 def manhattan(self):
258386 return abs(self.x) + abs(self.y)
···264392 def neighbours_4(self):
265393 return [self + p for p in DIRS_4]
266394395395+ def neighbors_4(self):
396396+ return self.neighbours_4()
397397+398398+ def neighbours(self):
399399+ return self.neighbours_4()
400400+401401+ def neighbors(self):
402402+ return self.neighbours()
403403+267404 def neighbours_8(self):
268405 return [self + p for p in DIRS_8]
406406+407407+ def neighbors_8(self):
408408+ return self.neighbours_8()
269409270410271411DIRS_4 = DIRS = [
+64
2020/vm.py
···11+import os # NOQA
22+import sys # NOQA
33+import re # NOQA
44+import math # NOQA
55+import time # NOQA
66+import fileinput
77+from string import ascii_uppercase, ascii_lowercase # NOQA
88+from collections import Counter, defaultdict, deque, namedtuple # NOQA
99+from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA
1010+1111+from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA
1212+from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA
1313+from utils import new_table, transposed, rotated # NOQA
1414+from utils import md5, sha256, knot_hash # NOQA
1515+from utils import VOWELS, CONSONANTS # NOQA
1616+from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0)
1717+1818+# Itertools Functions:
1919+# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
2020+# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC
2121+# combinations('ABCD', 2) AB AC AD BC BD CD
2222+# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD
2323+2424+TAPE = []
2525+2626+for line in fileinput.input():
2727+ ins = line.split(' ')[0]
2828+ TAPE.append([ins, parse_nums(line)])
2929+3030+3131+def emulate(tape, pc=0, acc=0, debug=False):
3232+ """Returns (acc, pc, regs, loop_detected)"""
3333+ breakpoints = set([])
3434+ seen = Counter()
3535+ regs = {}
3636+ while pc < len(tape):
3737+ if pc in seen:
3838+ pass
3939+ # print "Exiting on repeat PC:", pc
4040+ # return (acc, pc, regs, True)
4141+4242+ seen[pc] += 1
4343+4444+ ins, ops = tape[pc]
4545+ if debug:
4646+ if pc in breakpoints:
4747+ print '-------'
4848+ time.sleep(0.001)
4949+ print "[{:04d} @ {:03d}] {} {}\t\t{}\t\t{}".format(pc, seen[pc], ins, ops, acc, regs)
5050+5151+ if ins == 'acc':
5252+ acc += ops[0]
5353+ pc += 1
5454+ elif ins == 'jmp':
5555+ pc += ops[0]
5656+ elif ins == 'nop':
5757+ pc += 1
5858+ else:
5959+ print "UNKNOWN OPCODE", ins
6060+ pc += 1
6161+6262+ return (acc, pc, regs, False)
6363+6464+emulate(TAPE, debug=True)