diff --git a/2020/day01.py b/2020/day01.py new file mode 100644 index 0000000..b1c3010 --- /dev/null +++ b/2020/day01.py @@ -0,0 +1,17 @@ +import fileinput + +nums = [int(x) for x in fileinput.input()] + +part_1 = None +part_2 = None + +for m in nums: + for n in nums: + for o in nums: + if m + n == 2020: + part_1 = m * n + elif m + n + o == 2020: + part_2 = m * n * o + +print "Part 1:", part_1 +print "Part 2:", part_2 diff --git a/2020/day02.py b/2020/day02.py new file mode 100644 index 0000000..b554166 --- /dev/null +++ b/2020/day02.py @@ -0,0 +1,17 @@ +import fileinput +from utils import parse_line + +part_1 = 0 +part_2 = 0 + +for line in fileinput.input(): + start, end, letter, pwd = parse_line(r'(\d+)-(\d+) (\w+): (\w+)', line) + + if start <= pwd.count(letter) <= end: + part_1 += 1 + + if (pwd[start-1] == letter) ^ (pwd[end-1] == letter): + part_2 += 1 + +print "Part 1:", part_1 +print "Part 2:", part_2 diff --git a/2020/day03.py b/2020/day03.py new file mode 100644 index 0000000..cbfe753 --- /dev/null +++ b/2020/day03.py @@ -0,0 +1,30 @@ +import fileinput + + +grid = [line.strip() for line in fileinput.input()] + +max_y = len(grid) +max_x = len(grid[0]) + +part_1 = 0 +part_2 = 1 + +for xs, ys in ((1, 1), (3, 1), (5, 1), (7, 1), (1, 2)): + x = 0 + y = 0 + tot = 0 + + while y < max_y: + if grid[y][x] == '#': + tot += 1 + + x = (x + xs) % max_x + y += ys + + part_2 *= tot + + if (xs, ys) == (3, 1): + part_1 = tot + +print part_1 +print part_2 diff --git a/2020/day04.py b/2020/day04.py new file mode 100644 index 0000000..aa590f6 --- /dev/null +++ b/2020/day04.py @@ -0,0 +1,50 @@ +import fileinput +from collections import OrderedDict + +def validate_hgt(hgt): + if hgt.endswith('cm'): + return 150 <= int(hgt[:-2]) <= 193 + elif hgt.endswith('in'): + return 59 <= int(hgt[:-2]) <= 76 + + return False + +VALIDATION_FNS = { + 'byr': lambda v: 1920 <= int(v) <= 2002, + 'iyr': lambda v: 2010 <= int(v) <= 2020, + 'eyr': lambda v: 2020 <= int(v) <= 2030, + 'hgt': validate_hgt, + 'hcl': lambda v: len(v) == 7 and v[0] == '#' and all(c in 'abcdef1234567890' for c in v[1:]), + 'ecl': lambda v: v in ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth'], + 'pid': lambda v: len(v) == 9 and all(c in '1234567890' for c in v), +} + +# Parse problem input +passports = [] +port = OrderedDict() + +for line in fileinput.input(): + line = line.strip() + if not line: + passports.append(port) + port = OrderedDict() + else: + parts = [x.split(':') for x in line.split()] + for a, b in parts: + port[a] = b + +passports.append(port) + +part_1 = 0 +part_2 = 0 + +for p in passports: + if all(k in p for k in VALIDATION_FNS): + part_1 += 1 + + if all(VALIDATION_FNS.get(k, lambda x: True)(v) for k, v in p.items()): + part_2 += 1 + +print part_1 +print part_2 + diff --git a/2020/day05.py b/2020/day05.py new file mode 100644 index 0000000..84da822 --- /dev/null +++ b/2020/day05.py @@ -0,0 +1,24 @@ +import fileinput +from collections import Counter + + +seen = set() +max_seat_id = 0 +min_row = 1000 +max_row = 0 + +for line in fileinput.input(): + row = int(line[:7].replace('F', '0').replace('B', '1'), 2) + col = int(line[7:10].replace('L', '0').replace('R', '1'), 2) + + min_row = min(row, min_row) + max_row = max(row, max_row) + max_seat_id = max(row * 8 + col, max_seat_id) + seen.add((row, col)) + +print "Highest seat ID:", max_seat_id + +for row in range(min_row + 1, max_row - 1): + for col in range(8): + if (row, col) not in seen: + print "Your seat ID:", row * 8 + col diff --git a/2020/day06.py b/2020/day06.py new file mode 100644 index 0000000..90478fb --- /dev/null +++ b/2020/day06.py @@ -0,0 +1,22 @@ +import fileinput +from string import ascii_lowercase + +data = ''.join([line for line in fileinput.input()]) +groups = [g.split('\n') for g in data.split('\n\n')] + +part_1 = 0 +part_2 = 0 + +for group in groups: + anyone = set() + everyone = set(ascii_lowercase) + + for person in group: + anyone |= set(person) + everyone &= set(person) + + part_1 += len(anyone) + part_2 += len(everyone) + +print part_1 +print part_2 diff --git a/2020/day07.py b/2020/day07.py new file mode 100644 index 0000000..7886e21 --- /dev/null +++ b/2020/day07.py @@ -0,0 +1,49 @@ +import fileinput +from collections import defaultdict +from utils import parse_line + +GOAL = 'shiny gold' + +graph = defaultdict(set) + +for i, line in enumerate(fileinput.input()): + line = line.strip() + color, rest = parse_line(r'(\w+ \w+) bags contain (.+)+', line) + rest = rest.replace('.', '').split(',') + + for r in rest: + if r != 'no other bags': + parts = r.split() + new_color = ' '.join(parts[1:3]) + graph[color].add((new_color, int(parts[0]))) + + +def dfs_1(node): + if node == GOAL: + return True + + return any(dfs_1(n) for n, _ in graph[node]) + + +def dfs_2(node, count=1): + tot = 1 + + for nxt, amt in graph[node]: + tot += dfs_2(nxt, amt) + + return count * tot + +part_1 = 0 + +keys = graph.keys() + +for bag in keys: + print bag + if dfs_1(bag): + part_1 += 1 + print "good" + +print part_1 - 1 + + +print dfs_2(GOAL) - 1 diff --git a/2020/day08.py b/2020/day08.py new file mode 100644 index 0000000..76cdc32 --- /dev/null +++ b/2020/day08.py @@ -0,0 +1,49 @@ +import fileinput +from copy import deepcopy +from utils import parse_nums + +TAPE = [] + +for line in fileinput.input(): + ins = line.split(' ')[0] + TAPE.append([ins, parse_nums(line)]) + + +def emulate(tape, pc=0, acc=0): + """Returns (acc, pc, loop_detected)""" + seen = set() + while pc < len(tape): + if pc in seen: + return (acc, pc, True) + else: + seen.add(pc) + + ins, ops = tape[pc] + + if ins == 'acc': + acc += ops[0] + pc += 1 + elif ins == 'jmp': + pc += ops[0] + else: + pc += 1 + + return (acc, pc, False) + + +print "ACC at repeated instruction:", emulate(TAPE)[0] + +for i in range(len(TAPE)): + tape = deepcopy(TAPE) + + if tape[i][0] == 'acc': + continue + else: + if tape[i][0] == 'jmp': + tape[i][0] = 'nop' + else: # ins == 'nop' + tape[i][0] = 'jmp' + + acc, pc, loop_detected = emulate(tape) + if not loop_detected: + print "ACC with modified termination:", acc diff --git a/2020/day09.py b/2020/day09.py new file mode 100644 index 0000000..790502f --- /dev/null +++ b/2020/day09.py @@ -0,0 +1,22 @@ +import fileinput +from itertools import permutations + +SEQ = [int(x) for x in fileinput.input()] +LEN = 25 + +for i in range(LEN, len(SEQ)): + for x, y in permutations(SEQ[i-LEN:i], 2): + if x + y == SEQ[i]: + break + else: + INVALID = SEQ[i] + print "Part 1:", INVALID + break + +for n in range(2, len(SEQ)): + tot = 0 + for i in range(len(SEQ)-n): + tot = sum(SEQ[i:i+n]) + if tot == INVALID: + print "Part 2:", min(SEQ[i:i+n]) + max(SEQ[i:i+n]) + diff --git a/2020/day10.py b/2020/day10.py new file mode 100644 index 0000000..58e9170 --- /dev/null +++ b/2020/day10.py @@ -0,0 +1,39 @@ +import fileinput +from collections import Counter +from utils import memoize + +NUMS = [int(x) for x in fileinput.input()] + +NUMS.append(0) +NUMS.append(max(NUMS) + 3) +NUMS.sort() + +deltas = Counter() +curr = 0 + +for n in NUMS: + deltas[n - curr] += 1 + curr = n + +print "Part 1:", deltas[1] * deltas[3] + + +@memoize +def dp(i): + if i >= len(NUMS) - 1: + return 1 + + ways = 0 + + if i + 1 < len(NUMS) and NUMS[i + 1] - NUMS[i] <= 3: + ways += dp(i + 1) + + if i + 2 < len(NUMS) and NUMS[i + 2] - NUMS[i] <= 3: + ways += dp(i + 2) + + if i + 3 < len(NUMS) and NUMS[i + 3] - NUMS[i] <= 3: + ways += dp(i + 3) + + return ways + +print "Part 2:", dp(0) diff --git a/2020/day11.py b/2020/day11.py new file mode 100644 index 0000000..07582d0 --- /dev/null +++ b/2020/day11.py @@ -0,0 +1,119 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from heapq import heappush, heappop, heapify +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +# Itertools Functions: +# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD +# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC +# combinations('ABCD', 2) AB AC AD BC BD CD +# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD + +tot = 0 +res = [] +table = new_table(None, width=2, height=4) +graph = {} + +# Uncomment for multi-group style inputs. :c +# data = ''.join([line for line in fileinput.input()]) +# groups = [g.split('\n') for g in data.split('\n\n')] + +for i, line in enumerate(fileinput.input()): + line = line.strip() + nums = parse_nums(line) + data = parse_line(r'', line) + + for j, c in enumerate(line): + graph[Point(j, i)] = c + + res.append(line) + + if i == 0: + print(data) + +max_x = len(res[0]) +max_y = len(res) + +print graph +print res + +state = graph + +while True: + new_state = {} + + for y in range(max_y): + for x in range(max_x): + p = Point(x, y) + occ = 0 + # for n in p.neighbours_8(): + # if state.get(n, '.') == '#': + # occ += 1 + + for d in DIRS_8: + np = p + d + # np = p + while 0 <= np.x < max_x and 0 <= np.y < max_y: + if state.get(np, '.') == '.': + np += d + # break + elif state.get(np, '.') == '#': + occ += 1 + break + else: + break + + + if state[p] == 'L' and occ == 0: + new_state[p] = '#' + + elif state[p] == '#' and occ >= 5: + new_state[p] = 'L' + + else: + new_state[p] = state[p] + + + + a = '' + b = '' + for y in range(max_y): + for x in range(max_x): + p = Point(x, y) + # print state[p], + a += state[p] + b += new_state[p] + + # print + + # print + # print + + if a == b: + tot = 0 + for y in range(max_y): + for x in range(max_x): + if new_state[Point(x, y)] == '#': + tot += 1 + + print tot + + break + + + state = new_state + + diff --git a/2020/day12.py b/2020/day12.py new file mode 100644 index 0000000..cb0abf7 --- /dev/null +++ b/2020/day12.py @@ -0,0 +1,35 @@ +import fileinput +from utils import Point, DIRS + +DIR_MAP = 'NESW' + +p1 = Point(0, 0) +p2 = Point(0, 0) +wp = Point(10, 1) + +facing = 1 # start at 1 to match DIRS + +for line in fileinput.input(): + d = line[0] + n = int(line[1:]) + + if d in DIR_MAP: + delta = DIRS[DIR_MAP.index(d)] * n + p1 += delta + wp += delta + + elif d == 'L' or d == 'R': + turns = n // 90 + if d == 'L': + turns = 4 - turns + + facing = (facing + turns) % 4 + + wp = wp.rotate(turns) + + elif d == 'F': + p1 += DIRS[facing] * n + p2 += wp * n + +print "Part 1:", p1.manhattan +print "Part 2:", p2.manhattan diff --git a/2020/day13.py b/2020/day13.py new file mode 100644 index 0000000..d31ec85 --- /dev/null +++ b/2020/day13.py @@ -0,0 +1,69 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import chunks, gcd, lcm, crt, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +# Itertools Functions: +# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD +# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC +# combinations('ABCD', 2) AB AC AD BC BD CD +# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD + +tot = 0 +res = [] +board = {} +table = new_table(None, width=2, height=4) + +timestamp = None +buses = [] + +# Uncomment for multi-group style inputs. :c +# data = ''.join([line for line in fileinput.input()]) +# groups = [g.split('\n') for g in data.split('\n\n')] + +for y, line in enumerate(fileinput.input()): + line = line.strip() + nums = parse_nums(line) + data = parse_line(r'', line) + + for x, c in enumerate(line): + board[Point(x, y)] = c + + if y == 0: + timestamp = int(line) + else: + for x in line.split(','): + if x == 'x': + buses.append(None) + else: + buses.append(int(x)) + +m = [] +x = [] +for i, b in enumerate(buses): + if b: + m.append(b) + x.append(i) + +print crt(x, m) + + + +num = 1777307553937916 +l1 = 2265213528143033 + +num2 = num - l1 + +print abs(num2) < abs(num) diff --git a/2020/day14.py b/2020/day14.py new file mode 100644 index 0000000..a23aa42 --- /dev/null +++ b/2020/day14.py @@ -0,0 +1,83 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +# Itertools Functions: +# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD +# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC +# combinations('ABCD', 2) AB AC AD BC BD CD +# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD + +tot = 0 +res = [] +board = {} +table = new_table(None, width=2, height=4) + +# Uncomment for multi-group style inputs. :c +# data = ''.join([line for line in fileinput.input()]) +# groups = [g.split('\n') for g in data.split('\n\n')] + +for y, line in enumerate(fileinput.input()): + line = line.strip() + nums = parse_nums(line) + data = parse_line(r'', line) + + a, b = line.split(' = ') + # print parts + + if a == 'mask': + res.append((None, b)) + else: + res.append((nums[0], nums[1])) + +mem = defaultdict() + +mask = None + +for a, b in res: + if a is None: + mask = b + else: + addr = a + write = 0 + floats = [i for i, c in enumerate(reversed(mask)) if c == 'X'] + # print floats + for i, c in enumerate(reversed(mask)): + if c == '1': + write |= (1 << i) + elif c == '0': + write |= (addr & (1 << i)) + + writes = [write] + print writes, floats + for i in floats: + new_writes = [] + for w in writes: + print w + new_writes.append(w & ~(1 << i)) + new_writes.append(w | (1 << i)) + + print new_writes + writes = new_writes + + print writes + + + for w in writes: + mem[w] = b + +print sum(mem.values()) + diff --git a/2020/day15.py b/2020/day15.py new file mode 100644 index 0000000..0d17670 --- /dev/null +++ b/2020/day15.py @@ -0,0 +1,77 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +# Itertools Functions: +# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD +# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC +# combinations('ABCD', 2) AB AC AD BC BD CD +# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD + +tot = 0 +res = [] +board = {} +table = new_table(None, width=2, height=4) + +# Uncomment for multi-group style inputs. :c +# data = ''.join([line for line in fileinput.input()]) +# groups = [g.split('\n') for g in data.split('\n\n')] + +for y, line in enumerate(fileinput.input()): + line = line.strip() + nums = parse_nums(line) + data = parse_line(r'', line) + nums = [int(x) for x in line.split(',')] + + for x, c in enumerate(line): + board[Point(x, y)] = c + + if y == 0: + print(data) + +print nums + + + +seen = defaultdict(list) + +for i, n in enumerate(nums): + seen[n].append(i) + +print seen + +spoken = nums[-1] +i = len(nums) + +while True: + # print spoken, i + if len(seen[spoken]) == 1: + new_spoke = 0 + else: + new_spoke = i - seen[spoken][-2] - 1 + + # print "newspoke", new_spoke + + spoken = new_spoke + seen[spoken].append(i) + + i += 1 + if i == 30000000: + break + +print spoken + + diff --git a/2020/day16.py b/2020/day16.py new file mode 100644 index 0000000..938f620 --- /dev/null +++ b/2020/day16.py @@ -0,0 +1,43 @@ +import fileinput +from collections import defaultdict +from utils import parse_nums, mul, transposed, resolve_mapping + +fields = {} +tickets = [] + +for line in fileinput.input(): + nums = parse_nums(line) + if nums: + if len(nums) == 4: + fields[line.split(':')[0]] = [abs(x) for x in nums] + else: + tickets.append(nums) + +valid_tickets = [] +part_1 = 0 + +for ticket in tickets[1:]: + for n in ticket: + for a, b, c, d in fields.values(): + if a <= n <= b or c <= n <= d: + break + + else: + part_1 += n + break + else: + valid_tickets.append(ticket) + +print "Part 1:", part_1 + +poss = defaultdict(set) + +for i, col in enumerate(transposed(valid_tickets)): + for field, (a, b, c, d) in fields.items(): + if all(a <= n <= b or c <= n <= d for n in col): + poss[field].add(i) + +resolved = resolve_mapping(poss) + +departures = [resolved[field] for field in resolved if 'departure' in field] +print "Part 2:", mul(tickets[0][i] for i in departures) diff --git a/2020/day17.py b/2020/day17.py new file mode 100644 index 0000000..787239d --- /dev/null +++ b/2020/day17.py @@ -0,0 +1,52 @@ +import fileinput +from collections import defaultdict +from itertools import product + +grid_1 = defaultdict(lambda: '.') +grid_2 = defaultdict(lambda: '.') + +for y, line in enumerate(fileinput.input()): + for x, c in enumerate(line.strip()): + grid_1[x, y, 0] = c + grid_2[x, y, 0, 0] = c + +max_dim = max(x, y) + + +for i in range(6): + new_grid_1 = defaultdict(lambda: '.') + new_grid_2 = defaultdict(lambda: '.') + + for x, y, z, w in product(range(-i - 1, max_dim + i + 1), repeat=4): + neighs_1 = 0 + neighs_2 = 0 + + for dx, dy, dz, dw in product(range(-1, 2), repeat=4): + if dx == 0 and dy == 0 and dz == 0 and dw == 0: + continue + + if grid_1[x + dx, y + dy, z + dz] == '#' and dw == 0: + neighs_1 += 1 + if grid_2[x + dx, y + dy, z + dz, w + dw] == '#': + neighs_2 += 1 + + + if grid_1[x, y, z] == '#' and (neighs_1 == 2 or neighs_1 == 3): + new_grid_1[x, y, z] = '#' + + elif grid_1[x, y, z] == '.' and neighs_1 == 3: + new_grid_1[x, y, z] = '#' + + if grid_2[x, y, z, w] == '#' and (neighs_2 == 2 or neighs_2 == 3): + new_grid_2[x, y, z, w] = '#' + + elif grid_2[x, y, z, w] == '.' and neighs_2 == 3: + new_grid_2[x, y, z, w] = '#' + + grid_1 = new_grid_1 + grid_2 = new_grid_2 + + +print "Part 1:", sum(c == '#' for c in grid_1.values()) +print "Part 2:", sum(c == '#' for c in grid_2.values()) + diff --git a/2020/day18.py b/2020/day18.py new file mode 100644 index 0000000..8092046 --- /dev/null +++ b/2020/day18.py @@ -0,0 +1,107 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +# Itertools Functions: +# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD +# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC +# combinations('ABCD', 2) AB AC AD BC BD CD +# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD + +tot = 0 +res = [] +board = {} +table = new_table(None, width=2, height=4) + +# Uncomment for multi-group style inputs. :c +# data = ''.join([line for line in fileinput.input()]) +# groups = [g.split('\n') for g in data.split('\n\n')] + +for y, line in enumerate(fileinput.input()): + line = line.strip().replace(' ', '') + nums = parse_nums(line) + data = parse_line(r'', line) + + for x, c in enumerate(line): + board[Point(x, y)] = c + + if y == 0: + print(data) + + res.append(line) + +print tot + +NUMS = '0123456789' + + +def is_number(str): + try: + int(str) + return True + except ValueError: + return False + +def is_name(str): + return re.match("\w+", str) + +def peek(stack): + return stack[-1] if stack else None + +def apply_operator(operators, values): + operator = operators.pop() + right = values.pop() + left = values.pop() + values.append(eval("{0}{1}{2}".format(left, operator, right))) + +def greater_precedence(op1, op2): + precedences = {'+' : 1, '-' : 0, '*' : 0, '/' : 0} + return precedences[op1] > precedences[op2] + +def evaluate(expression): + tokens = re.findall("[+/*()-]|\d+", expression) + values = [] + operators = [] + for token in tokens: + if is_number(token): + values.append(int(token)) + elif token == '(': + operators.append(token) + elif token == ')': + top = peek(operators) + while top is not None and top != '(': + apply_operator(operators, values) + top = peek(operators) + operators.pop() # Discard the '(' + else: + # Operator + top = peek(operators) + while top is not None and top not in "()" and greater_precedence(top, token): + apply_operator(operators, values) + top = peek(operators) + operators.append(token) + while peek(operators) is not None: + apply_operator(operators, values) + + return values[0] + + +for line in res: + a = evaluate(line) + print a, '=', line + tot += a + +print tot diff --git a/2020/day19.py b/2020/day19.py new file mode 100644 index 0000000..3c4b457 --- /dev/null +++ b/2020/day19.py @@ -0,0 +1,71 @@ +import fileinput +from collections import defaultdict +from utils import parse_nums, memoize + +@memoize +def resolve(r): + if type(rules[r]) == str: + return [rules[r]] + + matches = [] + for subrule in rules[r]: + submatches = [''] + for n in subrule: + new = [] + for m in resolve(n): + for existing in submatches: + new.append(existing + m) + + submatches = new + + matches.extend(submatches) + + return matches + +rules = defaultdict(list) +messages = [] + +for line in fileinput.input(): + line = line.strip() + nums = parse_nums(line) + + if nums: + parts = line.split(": ")[1].split(" | ") + r = nums[0] + for p in parts: + if '"' in p: + rules[r] = p[1] + else: + rules[r].append([int(x) for x in p.split(' ')]) + + elif line: + messages.append(line) + + +pl = len(resolve(42)[0]) + +part_1 = 0 +part_2 = 0 + +for line in messages: + if line in resolve(0): + part_1 += 1 + + orig_line = line + a = 0 + b = 0 + + while line[:pl] in resolve(42): + line = line[pl:] + a += 1 + + while line[:pl] in resolve(31): + line = line[pl:] + b += 1 + + if a > b and b > 0 and not line: + print orig_line + part_2 += 1 + +print "Part 1:", part_1 +print "Part 2:", part_2 diff --git a/2020/day20-solve.py b/2020/day20-solve.py new file mode 100644 index 0000000..6d8b178 --- /dev/null +++ b/2020/day20-solve.py @@ -0,0 +1,329 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from collections import Counter, defaultdict, deque, namedtuple # NOQA + +from utils import parse_nums, mul, print_grid, transposed, rotated, Point, new_table + +tot = 0 +res = [] +board = new_table('.', width=10, height=10) + +tiles = {} +curr = 0 +sy = 0 + +MONSTER = """ + # +# ## ## ### + # # # # # # +""" + +for y, line in enumerate(fileinput.input()): + line = line.strip() + nums = parse_nums(line) + + if not line: + tiles[curr] = copy.deepcopy(board) + board = new_table('.', width=10, height=10) + + if nums: + curr = nums[0] + sy = y + continue + + for x, c in enumerate(line): + board[y - sy - 1][x] = c + +tiles[curr] = copy.deepcopy(board) + +edges = Counter() +piece_edges = defaultdict(set) + +# find edges: +for id, t in tiles.items(): + x = copy.deepcopy(t) + for _ in range(4): + row = ''.join(x[0]) + edges[row] += 1 + edges[row[::-1]] += 1 + piece_edges[row].add(id) + piece_edges[row[::-1]].add(id) + x = rotated(x) + +# for c in edges.most_common(): +# print c + +cands = [] + +for id, tile in tiles.items(): + ones = 0 + for _ in range(4): + row = ''.join(x[0]) + if edges.get(row) == 1: + ones += 1 + if edges.get(row[::-1]) == 1: + ones += 1 + x = rotated(x) + + if ones >= 1: + cands.append(id) + + # print ones + +graph = defaultdict(set) + +for p, e in piece_edges.items(): + # print p, e + for x in e: + for y in e: + graph[x].add(y) + graph[y].add(x) + + +tot = 1 +corners = [] +for k, v in graph.items(): + # print k, v + if len(v) == 3: + corners.append(k) + # print k + tot *= k + +# print tot + +puzzle = {} +x = 0 +y = 0 + +start = corners[0] +puzzle[0, 0] = start +o = [i for i in graph[start] if i != start] +puzzle[0, 1] = o[0] +puzzle[1, 0] = o[1] + + + +m = int(math.sqrt(len(tiles))) +x = 2 +y = 2 + +while x < m: + for k, v in graph.items(): + if x == m - 1: + if len(v) == 3 and k in graph[puzzle[x - 1, 0]] and k not in puzzle.values(): + puzzle[x, 0] = k + x += 1 + break + else: + if len(v) == 4 and k in graph[puzzle[x - 1, 0]] and k not in puzzle.values(): + puzzle[x, 0] = k + x += 1 + break + +while y < m: + for k, v in graph.items(): + if y == m - 1: + if len(v) == 3 and k in graph[puzzle[0, y - 1]] and k not in puzzle.values(): + puzzle[0, y] = k + y += 1 + break + else: + if len(v) == 4 and k in graph[puzzle[0, y - 1]] and k not in puzzle.values(): + puzzle[0, y] = k + y += 1 + break + +# print puzzle + + +for y in range(1, m): + for x in range(1, m): + # print x, y + for k, v in graph.items(): + # print k, v, puzzle.get((x, y - 1)), puzzle.get((x - 1, y)) + if puzzle.get((x, y - 1)) in v and puzzle.get((x - 1, y)) in v and k not in puzzle.values(): + puzzle[x, y] = k + +assert len(puzzle) == len(tiles) + +print "Piece arrangement:" +for y in range(m): + print ' '.join(str(puzzle[x, y]) for x in range(m)) +print + +PUZZLE = {} + +for y in range(m): + for x in range(m): + PUZZLE[x, y] = tiles[puzzle[x, y]] + +row = ''.join(PUZZLE[0, 0][-1]) +col = ''.join(transposed(PUZZLE[0, 0])[-1]) + +# print row, piece_edges[row] +# print col, piece_edges[col] + +for _ in range(2): + for _ in range(4): + row = ''.join(PUZZLE[0, 0][-1]) + col = ''.join(transposed(PUZZLE[0, 0])[-1]) + # print_grid(PUZZLE[0, 0]) + # print + if puzzle[0, 1] in piece_edges[row] and puzzle[1, 0] in piece_edges[col]: + # print "good" + break + + PUZZLE[0, 0] = rotated(PUZZLE[0, 0]) + + else: + PUZZLE[0, 0] = transposed(PUZZLE[0, 0]) + continue + + break + +# print puzzle[0, 0], puzzle[0, 1], puzzle[1, 0] +# print_grid(PUZZLE[0, 0]) + +# x, 0 +for x in range(1, m): + for _ in range(2): + for _ in range(4): + row = ''.join(transposed(PUZZLE[x, 0])[0]) + # print "trying", row + if row == ''.join(transposed(PUZZLE[x - 1, 0])[-1]): + # print "good for x=", x + # print_grid(PUZZLE[x, 0]) + break + + PUZZLE[x, 0] = rotated(PUZZLE[x, 0]) + + else: + PUZZLE[x, 0] = transposed(PUZZLE[x, 0]) + continue + + break + + else: + print "no match for", x + +# y, 0 +for y in range(1, m): + for _ in range(2): + for _ in range(4): + row = ''.join(PUZZLE[0, y][0]) + # print "trying", row + if row == ''.join(PUZZLE[0, y - 1][-1]): + # print "good for y=", y + # print_grid(PUZZLE[0, y]) + break + + PUZZLE[0, y] = rotated(PUZZLE[0, y]) + + else: + PUZZLE[0, y] = transposed(PUZZLE[0, y]) + continue + + break + + else: + print "no match for", y + + +for y in range(1, m): + for x in range(1, m): + for _ in range(2): + for _ in range(4): + col = ''.join(transposed(PUZZLE[x, y])[0]) + row = ''.join(PUZZLE[x, y][0]) + + ocol = ''.join(transposed(PUZZLE[x - 1, y])[-1]) + orow = ''.join(PUZZLE[x, y - 1][-1]) + if row == orow and col == ocol: + # print "good for {}, {}".format(x, y) + # print_grid(PUZZLE[x, y]) + break + + PUZZLE[x, y] = rotated(PUZZLE[x, y]) + + else: + PUZZLE[x, y] = transposed(PUZZLE[x, y]) + continue + + break + + else: + print "no match for", x, y + +monster = [[None for _ in range(8 * m)] for _ in range(8 * m)] + +for y in range(m): + for x in range(m): + for j in range(8): + for i in range(8): + # print x, y, i, j + monster[y*8 + j][x*8 + i] = PUZZLE[x, y][j + 1][i + 1] + + +MONSTER = [] +MONSTER.append(" # ") +MONSTER.append("# ## ## ###") +MONSTER.append(" # # # # # # ") + +minds = [] + +mx = 20 +my = 3 +for y in range(len(MONSTER)): + for x, c in enumerate(MONSTER[y]): + if c == '#': + minds.append((x, y)) + +# print minds + +for _ in range(2): + monster = rotated(monster) + +# print_grid(monster) + +for _ in range(2): + for _ in range(4): + m_count = 0 + occupied = set() + for y in range(len(monster) - my): + for x in range(len(monster[0]) - mx): + if all(monster[y+j][x+i] == '#' for i, j in minds): + # print "monster!" + m_count += 1 + + for i, j in minds: + occupied.add((x+i, y+j)) + + if m_count > 1: + # print m_count, occupied + final_monster = {} + tot = 0 + for y in range(m * 8): + for x in range(m * 8): + p = Point(x, y) + if monster[y][x] == '#': + if (x, y) not in occupied: + tot += 1 + final_monster[p] = '~' + else: + final_monster[p] = 'O' + + else: + final_monster[p] = ' ' + + print_grid(final_monster) + print + print "Water roughness:", tot + + monster = rotated(monster) + + monster = transposed(monster) + diff --git a/2020/day20.py b/2020/day20.py new file mode 100644 index 0000000..7b3a331 --- /dev/null +++ b/2020/day20.py @@ -0,0 +1,173 @@ +import math +import fileinput +from itertools import permutations +from collections import Counter, defaultdict +from utils import mul, print_grid, transposed, rotated + + +TILES = defaultdict(list) + +# Read problem input +for line in fileinput.input(): + if line.startswith('Tile'): + id = int(line.replace(':', '').split(' ')[1]) + elif len(line) > 5: + TILES[id].append(line.strip()) + + +# Map edges to pieces that share that edge +piece_edges = defaultdict(set) + +for id, tile in TILES.items(): + for _ in range(4): + row = ''.join(tile[0]) + piece_edges[row].add(id) + piece_edges[row[::-1]].add(id) + tile = rotated(tile) + + +# Construct graph of neighbouring pieces +graph = defaultdict(set) + +for neighbouring_pieces in piece_edges.values(): + for x, y in permutations(neighbouring_pieces, 2): + graph[x].add(y) + + +# Corners are the only pieces that neighbour exactly 2 other tiles. +corners = [tile for tile, neighs in graph.items() if len(neighs) == 2] +print "Corner ID product:", mul(corners) +print + + +# Build up the full arrangement of the tiles. Arbitrarily pick +# a corner to be the top-left, and assign its neighbours. +puzzle = {} +puzzle[0, 0] = corners[0] + + +# Let `m` represent the m by m arrangement of tiles. +m = int(math.sqrt(len(TILES))) + + +# Assign tiles for the top row and left column by choosing from the subset +# of tiles that contain either 2 or 3 neighbours (ie. edges and corners). +# Make sure to not reuse tiles since that ruins this algorithm. +edges = [tile for tile, neighs in graph.items() if len(neighs) == 3] + +for x in range(1, m): + for tile in edges + corners: + if tile in graph[puzzle[x - 1, 0]] and tile not in puzzle.values(): + puzzle[x, 0] = tile + +for y in range(1, m): + for tile in edges + corners: + if tile in graph[puzzle[0, y - 1]] and tile not in puzzle.values(): + puzzle[0, y] = tile + + +# Now assign the remaining tiles. Since the previous tiles were already filled +# in, we can check the left and top neighbour for every candidate location, +# which will uniquely match a single tile's neighbour list. +for y in range(1, m): + for x in range(1, m): + for tile, neighbours in graph.items(): + left = puzzle[x - 1, y] + top = puzzle[x, y - 1] + + if left in neighbours and top in neighbours and tile not in puzzle.values(): + puzzle[x, y] = tile + +print "Resolved piece arrangement:" +for y in range(m): + print ' '.join(str(puzzle[x, y]) for x in range(m)) +print + + +# Now we need to actually orient all the pieces. Let `PUZZLE` be a dictionary +# that maps tile locations to 2D arrays representing the pieces themselves. +PUZZLE = {pos: TILES[id] for pos, id in puzzle.items()} + + +# Define some helper functions for manipulating tiles. +def orientations(tile): + """Generates all 8 orientations of a 2D array.""" + for _ in range(2): + for _ in range(4): + yield tile + tile = rotated(tile) + + tile = transposed(tile) + +def aligned(a, b): + """Returns if the final row or col of `a` matches the first row or col of `b`""" + return ( + ''.join(a[-1]) == ''.join(b[0]) or + ''.join(transposed(a)[-1]) == ''.join(transposed(b)[0]) + ) + + +# We need to get the top-left corner into an orientation where its bottom and +# right edges match the neighbours based on the arrangement we determined above. +for tile in orientations(PUZZLE[0, 0]): + bottom = ''.join(tile[-1]) + right = ''.join(transposed(tile)[-1]) + + if puzzle[0, 1] in piece_edges[bottom] and puzzle[1, 0] in piece_edges[right]: + PUZZLE[0, 0] = tile + break + + +# Align remaining tiles by matching them up via their top and left edges, if present. +for y in range(m): + for x in range(m): + if x == 0 and y == 0: + continue + + for tile in orientations(PUZZLE[x, y]): + if (x == 0 or aligned(PUZZLE[x - 1, y], tile)) and (y == 0 or aligned(PUZZLE[x, y - 1], tile)): + PUZZLE[x, y] = tile + break + + +# Process the puzzle to form the image (removing borders of tiles). +# Store as 2D array instead of dictionary so we can re-orient it. +IMG = [[None for _ in range(8 * m)] for _ in range(8 * m)] +for (x, y), tile in PUZZLE.items(): + for j in range(8): + for i in range(8): + IMG[y*8 + j][x*8 + i] = tile[j + 1][i + 1] + + +# Get the offset positions for the sea monster components. +MONSTER = """\ + # +# ## ## ### + # # # # # # +""" + +m_pos = [] +for y, row in enumerate(MONSTER.split('\n')): + for x, c in enumerate(row): + if c == '#': + m_pos.append((x, y)) + + +# Search for sea monsters! +for image in orientations(IMG): + occupied = set() + for y in range(m * 8 - max(y for x, y in m_pos)): + for x in range(m * 8 - max(x for x, y in m_pos)): + if all(image[y+j][x+i] == '#' for i, j in m_pos): + for i, j in m_pos: + occupied.add((x+i, y+j)) + + if occupied: + final_img = {} + for y in range(m * 8): + for x in range(m * 8): + if image[y][x] == '#': + final_img[x, y] = 'O' if (x, y) in occupied else '~' + + print_grid(final_img) + print "\nWater roughness:", sum(1 for c in final_img.values() if c == '~') diff --git a/2020/day21.py b/2020/day21.py new file mode 100644 index 0000000..d37d38c --- /dev/null +++ b/2020/day21.py @@ -0,0 +1,62 @@ +import fileinput +from utils import resolve_mapping +from collections import Counter, defaultdict, deque, namedtuple # NOQA + +foods = [] + +for y, line in enumerate(fileinput.input()): + line = line.strip() + + ingredients, allergens = line.split(' (') + allergens = allergens.replace(')', '').replace(',', '').split(' ')[1:] + + foods.append((ingredients.split(' '), allergens)) + +ing_to_all = defaultdict(set) +all_to_ing = defaultdict(set) + +for ingredients, allergens in foods: + for i in ingredients: + for a in allergens: + ing_to_all[i].add(a) + all_to_ing[a].add(i) + +candidates = defaultdict(set) +resolved_ing = set() + +for a in all_to_ing: + poss = [] + for ingredients, allergens in foods: + if a in allergens: + poss.append(ingredients) + + if poss: + resolve = set(poss[0]) + for p in poss[1:]: + resolve &= set(p) + + for r in resolve: + candidates[a].add(r) + resolved_ing.add(r) + else: + print "empty poss", a + +good = set(ing_to_all) - resolved_ing +print len(good) + +tot = 0 +for ingredients, allergens in foods: + for i in ingredients: + if i in good: + tot += 1 + + +print tot + +resolved = resolve_mapping(candidates) + +l = [] +for r in sorted(resolved): + l.append(resolved[r]) + +print ','.join(l) diff --git a/2020/day22.py b/2020/day22.py new file mode 100644 index 0000000..5b154e9 --- /dev/null +++ b/2020/day22.py @@ -0,0 +1,92 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import copy # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA +from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +tot = 0 +res = [] +board = {} +table = new_table(None, width=2, height=4) + +p1 = deque() +p2 = deque() + +is1 = True + +for y, line in enumerate(fileinput.input()): + line = line.strip() + nums = parse_nums(line) + data = parse_line(r'', line) + + if ':' in line: + continue + + if not line: + is1 = False + + if nums: + if is1: + p1.append(nums[0]) + else: + p2.append(nums[0]) + + +def serialize(p1, p2): + a = ','.join(str(x) for x in p1) + b = ','.join(str(x) for x in p2) + return a + '~' + b + +SEEN = set() + +depth = 0 + +@memoize +def combat(p1, p2): + # print "triggered new combat", p1, p2 + while p1 and p2: + state = serialize(p1, p2) + if state in SEEN: + return p1, True + + SEEN.add(state) + + a = p1.popleft() + b = p2.popleft() + + if len(p1) >= a and len(p2) >= b: + # print "triggering combat for", a, b + p1_win = combat(deque(list(p1)[:a]), deque(list(p2)[:b]))[1] + else: + p1_win = a > b + + if p1_win: + p1.append(a) + p1.append(b) + else: + p2.append(b) + p2.append(a) + + return p1 if p1 else p2, True if p1 else False + + +deck, winner = combat(p1, p2) +print deck, winner + + +ans = 0 +for i, a in enumerate(reversed(deck), start = 1): + ans += i * a + +print ans diff --git a/2020/day23.py b/2020/day23.py new file mode 100644 index 0000000..9a44107 --- /dev/null +++ b/2020/day23.py @@ -0,0 +1,68 @@ +NUM_CUPS = 1000000 +ITERS = 10000000 + +class Cup: + def __init__(self, n): + self.n = n + + def __repr__(self): + return "{} -> ({}) -> {}".format(self.prev.n, self.n, self.next.n) + +labeling = [int(x) for x in raw_input()] +cup_ptrs = {} + +for i in range(1, NUM_CUPS + 1): + cup_ptrs[i] = Cup(i) + +curr_cup = None +prev = None +for n in labeling: + if curr_cup is None: + curr_cup = cup_ptrs[n] + prev = curr_cup + else: + cup_ptrs[n].prev = prev + prev.next = cup_ptrs[n] + prev = cup_ptrs[n] + +for n in range(len(labeling) + 1, NUM_CUPS + 1): + cup_ptrs[n].prev = prev + prev.next = cup_ptrs[n] + prev = cup_ptrs[n] + +curr_cup.prev = prev +prev.next = curr_cup + +for move in range(ITERS): + curr = curr_cup.n + + # Pick up three cups + a = curr_cup.next + b = a.next + c = b.next + + # Cup spacing adjusted + a.prev.next = c.next + c.next.prev = a.prev + + dval = curr - 1 + if dval == 0: + dval = NUM_CUPS + while dval == a.n or dval == b.n or dval == c.n: + dval -= 1 + if dval == 0: + dval = NUM_CUPS + + dest_cup = cup_ptrs[dval] + tmp_cup = dest_cup.next + + dest_cup.next = a + a.prev = dest_cup + + c.next = tmp_cup + tmp_cup.prev = c + + # Move current cup to the next cup. + curr_cup = curr_cup.next + +print cup_ptrs[1].next.n * cup_ptrs[1].next.next.n diff --git a/2020/day24.py b/2020/day24.py new file mode 100644 index 0000000..4ba2163 --- /dev/null +++ b/2020/day24.py @@ -0,0 +1,62 @@ +import fileinput +from collections import defaultdict + +HEX_DIRS = { + 'e': (1, -1, 0), + 'ne': (1, 0, -1), + 'se': (0, -1, 1), + 'w': (-1, 1, 0), + 'sw': (-1, 0, 1), + 'nw': (0, 1, -1), +} + +def hex_neighbours(x, y, z): + for dx, dy, dz in HEX_DIRS.values(): + yield x + dx, y + dy, z + dz + + +TILES = defaultdict(bool) + +for line in fileinput.input(): + x = 0 + y = 0 + z = 0 + curr = '' + + for c in line: + curr += c + if curr in HEX_DIRS: + dx, dy, dz = HEX_DIRS[curr] + x += dx + y += dy + z += dz + curr = '' + + TILES[x, y, z] = not TILES[x, y, z] + +print "Part 1:", sum(TILES.values()) + +for day in range(1, 100 + 1): + new_tiles = defaultdict(bool) + + for old_pos in TILES.keys(): + for pos in hex_neighbours(*old_pos): + neighs = 0 + for n in hex_neighbours(*pos): + if TILES[n]: + neighs += 1 + + if TILES[pos] and (neighs == 0 or neighs > 2): + pass + elif not TILES[pos] and neighs == 2: + new_tiles[pos] = True + else: + if TILES[pos]: + new_tiles[pos] = True + + TILES = new_tiles + +print "Part 2:", sum(TILES.values()) + + + diff --git a/2020/day25.py b/2020/day25.py new file mode 100644 index 0000000..6ea03c4 --- /dev/null +++ b/2020/day25.py @@ -0,0 +1,19 @@ +import sys +import fileinput + + +MOD = 20201227 +KEYS = [int(n) for n in fileinput.input()] + +card_pub = KEYS[0] +door_pub = KEYS[1] + + +for loop_size in range(1, MOD + 1): + val = pow(7, loop_size, MOD) + if val == card_pub: + print pow(door_pub, loop_size, MOD) + break + elif val == door_pub: + print pow(card_pub, loop_size, MOD) + break diff --git a/2020/search.py b/2020/search.py new file mode 100644 index 0000000..6501b6d --- /dev/null +++ b/2020/search.py @@ -0,0 +1,51 @@ +def bfs(start, graph): + from collections import deque + + max_depth = 0 + depths = {} + horizon = deque([(start, 0)]) # node, depth + seen = {start: None} + + # TODO + def is_goal(node): + return graph.get(node) == 'G' + + def gen_neighbours(node): + for n in node.neighbours_4(): + if graph.get(n, ' ') != ' ': + yield n + + + while horizon: + node, depth = horizon.popleft() # pop() for DFS + depths[node] = depth + + if depth > max_depth: + max_depth = depth + print "BFS @ {}; depth={}, horizon={}, seen={}".format(node, depth, len(horizon), len(seen)) + + for new in gen_neighbours(node): + if new in seen: + continue + + seen[new] = node + horizon.append((new, depth + 1)) + + if is_goal(new): + print "FOUND GOAL", new, depth + 1 + path = [] + curr = new + while curr is not None: + path.append(curr) + curr = seen[curr] + for p in reversed(path): + print p + pass + print "FOUND GOAL", new, depth + 1 + return depth + 1 + + print "FLOOD FILL COMPLETE, max_depth={}, seen={}".format(max_depth, len(seen)) + return depths + +start = Point(0, 0) +bfs(start, board) diff --git a/2020/starter.py b/2020/starter.py index 02bc58b..996b2f7 100644 --- a/2020/starter.py +++ b/2020/starter.py @@ -2,17 +2,18 @@ import os # NOQA import sys # NOQA import re # NOQA import math # NOQA +import copy # NOQA import fileinput from string import ascii_uppercase, ascii_lowercase # NOQA from collections import Counter, defaultdict, deque, namedtuple # NOQA from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA -from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes, resolve_mapping # NOQA from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA from utils import new_table, transposed, rotated # NOQA from utils import md5, sha256, knot_hash # NOQA from utils import VOWELS, CONSONANTS # NOQA -from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) # Itertools Functions: # product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD @@ -20,14 +21,23 @@ from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # combinations('ABCD', 2) AB AC AD BC BD CD # combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD -total = 0 -result = [] +tot = 0 +res = [] +board = {} table = new_table(None, width=2, height=4) -for i, line in enumerate(fileinput.input()): +# Uncomment for multi-group style inputs. :c +# data = ''.join([line for line in fileinput.input()]) +# groups = [g.split('\n') for g in data.split('\n\n')] + +for y, line in enumerate(fileinput.input()): line = line.strip() nums = parse_nums(line) data = parse_line(r'', line) - if i == 0: + for x, c in enumerate(line): + board[Point(x, y)] = c + + if y == 0: print(data) + diff --git a/2020/utils.py b/2020/utils.py index bc48cc7..b8e68bb 100644 --- a/2020/utils.py +++ b/2020/utils.py @@ -2,6 +2,8 @@ import re import math import hashlib import operator +import copy +from collections import Counter from functools import total_ordering @@ -76,6 +78,70 @@ def lcm(a, b): return a * b / gcd(a, b) +def egcd(a, b): + x0, x1, y0, y1 = 1, 0, 0, 1 + while b: + q, a, b = a // b, b, a % b + x0, x1 = x1, x0 - q * x1 + y0, y1 = y1, y0 - q * y1 + return a, x0, y0 + +def modinv(a, n): + g, x, _ = egcd(a, n) + if g == 1: + return x % n + else: + raise ValueError("%d is not invertible mod %d" % (a, n)) + +def crt(rems, mods): + ''' Solve a system of modular equivalences via the Chinese Remainder Theorem. + Does not require pairwise coprime moduli. ''' + + # copy inputs + orems, omods = rems, mods + rems = list(rems) + mods = list(mods) + + newrems = [] + newmods = [] + + for i in range(len(mods)): + for j in range(i+1, len(mods)): + g = gcd(mods[i], mods[j]) + if g == 1: + continue + if rems[i] % g != rems[j] % g: + raise ValueError("inconsistent remainders at positions %d and %d (mod %d)" % (i, j, g)) + mods[j] //= g + + while 1: + # transfer any remaining gcds to mods[j] + g = gcd(mods[i], mods[j]) + if g == 1: + break + mods[i] //= g + mods[j] *= g + + if mods[i] == 1: + continue + + newrems.append(rems[i] % mods[i]) + newmods.append(mods[i]) + + rems, mods = newrems, newmods + + # standard CRT + s = 0 + n = 1 + for k in mods: + n *= k + + for i in range(len(mods)): + ni = n // mods[i] + s += rems[i] * modinv(ni, mods[i]) * ni + return s % n, n + + def min_max_xy(points): if len(points) == 0: return None, None, None, None @@ -93,23 +159,71 @@ def min_max_xy(points): return min_x, max_x, min_y, max_y -def print_grid(grid, f=None): +def print_grid(grid, f=None, quiet=False): if f is None: f = lambda x: x # NOQA + counts = Counter() + serialized = [] + if type(grid) is dict: positions = grid.keys() min_x, max_x, min_y, max_y = min_max_xy(positions) if type(positions[0]) is tuple: for y in range(min_y, max_y + 1): - print ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1)) + row = ''.join(f(grid.get((x, y), ' ')) for x in range(min_x, max_x + 1)) + if not quiet: + print row + serialized.append(row) + for c in row: + counts[c] += 1 + else: # (x, y) => point for y in range(min_y, max_y + 1): - print ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1)) + row = ''.join(f(grid.get(Point(x, y), ' ')) for x in range(min_x, max_x + 1)) + if not quiet: + print row + serialized.append(row) + for c in row: + counts[c] += 1 else: for y in range(len(grid)): - print ''.join(f(grid[y][x]) for x in range(len(grid[0]))) + row = ''.join(f(grid[y][x]) for x in range(len(grid[0]))) + if not quiet: + print row + serialized.append(row) + for c in row: + counts[c] += 1 + + # if not quiet: + # print "height={} ({} -> {})".format(max_y - min_y + 1, min_y, max_y) + # print "width={} ({} -> {})".format(max_x - min_x + 1, min_x, max_x) + # print "Statistics:" + # for item, num in counts.most_common(): + # print "{}: {}".format(item, num) + + return serialized + +def resolve_mapping(candidates): + resolved = {} + + # Ensure the mapping is key -> set(values). + candidates_map = {} + for k, v in candidates.items(): + candidates_map[k] = set(v) + + while len(resolved) < len(candidates_map): + for candidate in candidates_map: + if len(candidates_map[candidate]) == 1 and candidate not in resolved: + r = candidates_map[candidate].pop() + for c in candidates_map: + candidates_map[c].discard(r) + + resolved[candidate] = r + break + + return resolved def memoize(f): @@ -117,9 +231,10 @@ def memoize(f): cache = {} def _mem_fn(*args): - if args not in cache: - cache[args] = f(*args) - return cache[args] + hargs = (','.join(str(x) for x in args)) + if hargs not in cache: + cache[hargs] = f(*args) + return cache[hargs] _mem_fn.cache = cache return _mem_fn @@ -253,6 +368,19 @@ class Point: return math.atan2(self.y, self.x) return math.atan2(self.y - to.y, self.x - to.x) + def rotate(self, turns): + """Returns the rotation of the Point around (0, 0) `turn` times clockwise.""" + turns = turns % 4 + + if turns == 1: + return Point(self.y, -self.x) + elif turns == 2: + return Point(-self.x, -self.y) + elif turns == 3: + return Point(-self.y, self.x) + else: + return self + @property def manhattan(self): return abs(self.x) + abs(self.y) @@ -264,9 +392,21 @@ class Point: def neighbours_4(self): return [self + p for p in DIRS_4] + def neighbors_4(self): + return self.neighbours_4() + + def neighbours(self): + return self.neighbours_4() + + def neighbors(self): + return self.neighbours() + def neighbours_8(self): return [self + p for p in DIRS_8] + def neighbors_8(self): + return self.neighbours_8() + DIRS_4 = DIRS = [ Point(0, 1), # north diff --git a/2020/vm.py b/2020/vm.py new file mode 100644 index 0000000..0442278 --- /dev/null +++ b/2020/vm.py @@ -0,0 +1,64 @@ +import os # NOQA +import sys # NOQA +import re # NOQA +import math # NOQA +import time # NOQA +import fileinput +from string import ascii_uppercase, ascii_lowercase # NOQA +from collections import Counter, defaultdict, deque, namedtuple # NOQA +from itertools import count, product, permutations, combinations, combinations_with_replacement # NOQA + +from utils import parse_line, parse_nums, mul, all_unique, factors, memoize, primes # NOQA +from utils import chunks, gcd, lcm, print_grid, min_max_xy # NOQA +from utils import new_table, transposed, rotated # NOQA +from utils import md5, sha256, knot_hash # NOQA +from utils import VOWELS, CONSONANTS # NOQA +from utils import Point, DIRS, DIRS_4, DIRS_8 # NOQA # N (0, 1) -> E (1, 0) -> S (0, -1) -> W (-1, 0) + +# Itertools Functions: +# product('ABCD', repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD +# permutations('ABCD', 2) AB AC AD BA BC BD CA CB CD DA DB DC +# combinations('ABCD', 2) AB AC AD BC BD CD +# combinations_with_replacement('ABCD', 2) AA AB AC AD BB BC BD CC CD DD + +TAPE = [] + +for line in fileinput.input(): + ins = line.split(' ')[0] + TAPE.append([ins, parse_nums(line)]) + + +def emulate(tape, pc=0, acc=0, debug=False): + """Returns (acc, pc, regs, loop_detected)""" + breakpoints = set([]) + seen = Counter() + regs = {} + while pc < len(tape): + if pc in seen: + pass + # print "Exiting on repeat PC:", pc + # return (acc, pc, regs, True) + + seen[pc] += 1 + + ins, ops = tape[pc] + if debug: + if pc in breakpoints: + print '-------' + time.sleep(0.001) + print "[{:04d} @ {:03d}] {} {}\t\t{}\t\t{}".format(pc, seen[pc], ins, ops, acc, regs) + + if ins == 'acc': + acc += ops[0] + pc += 1 + elif ins == 'jmp': + pc += ops[0] + elif ins == 'nop': + pc += 1 + else: + print "UNKNOWN OPCODE", ins + pc += 1 + + return (acc, pc, regs, False) + +emulate(TAPE, debug=True)