diff --git a/day02.py b/day02.py new file mode 100644 index 0000000..ca86131 --- /dev/null +++ b/day02.py @@ -0,0 +1,47 @@ +from lib import format_main +from itertools import product +from operator import mul, add + +def primitive(operator): + def opcode(program, i): + val = lambda x: program[program[x]] + program[program[i+3]] = operator(val(i+1), val(i+2)) + return i+4 + return opcode + +opcodes = {1 : primitive(add), + 2 : primitive(mul), + 99 : lambda p, i: len(p) + } + +def interpret(program): + i = 0 + while i < len(program): + op = program[i] + i = opcodes[op](program, i) + return program + + + +def preproc(puzzle_input): + program = list(map(int, puzzle_input.split(','))) + init_prog = lambda a,b: program[:1] + [a, b] + program[3:] + return init_prog + + +def partI(init_prog): + end_state = interpret(init_prog(12, 2)) + return end_state[0] + +def partII(init_prog): + target = 19690720 + space = 2 * list(range(100)) + for noun, verb in product(space, space): + memory = init_prog(noun, verb) + end_state = interpret(memory) + if end_state[0] == target: + break + + return 100*noun + verb + +import solver diff --git a/day03.py b/day03.py new file mode 100644 index 0000000..b05468e --- /dev/null +++ b/day03.py @@ -0,0 +1,80 @@ +up = lambda v: (v[0], v[1] + 1) +down = lambda v: (v[0], v[1] - 1) +left = lambda v: (v[0] - 1, v[1]) +right = lambda v: (v[0] + 1, v[1]) + +moves = {'U' : up, + 'D' : down, + 'L' : left, + 'R' : right + } + +def memoize(f): + cache = dict() + def memf(path): + if path not in cache: + cache[path] = f(path) + return cache[path] + return memf + +def iterate(f, x, N): + for i in range(N): + yield x + x = f(x) + +@memoize +def lines(path): + pos = (0, 0) + r = [] + for move in path.split(','): + dir, steps = move[0], int(move[1:]) + line = list(iterate(moves[dir], pos, steps+1)) + r.append(line) + pos = line[-1] + return r + +def coordinates(path, corners=True): + return sum((line[1:len(line) - 1 + corners] + for line in lines(path)), []) + +def crossings(*paths): + return set.intersection(*(set(coordinates(path, corners=False)) + for path in paths)) + +def min_crossing(path1, path2, f): + return min(f(v) for v in crossings(path1, path2)) + +def preproc(puzzle_input): + return puzzle_input.split() + +def partI(paths): + key = lambda v: abs(v[0]) + abs(v[1]) + + path1, path2 = paths + return min_crossing(path1, path2, key) + +def partII(paths): + path1, path2 = paths + def key(c1, c2): + r = lambda v: c1.index(v) + c2.index(v) + 2 + return r + + def min_steps(p1, p2): + c1, c2 = coordinates(p1), coordinates(p2) + return min_crossing(p1, p2, key(c1, c2)) + + return min_steps(path1, path2) + +import solver + +tests = solver.Tests() +tests.add("R75,D30,R83,U83,L12,D49,R71,U7,L72\n" + + "U62,R66,U55,R34,D71,R55,D58,R83", + partI=159, + partII=610) + +tests.add("R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51\n" + + "U98,R91,D20,R16,D67,R40,U7,R15,U6,R7", + partI=135, + partII=410) + diff --git a/day04.py b/day04.py new file mode 100644 index 0000000..9f785b3 --- /dev/null +++ b/day04.py @@ -0,0 +1,26 @@ + + +def preproc(puzzle_input): + space = tuple(map(int, puzzle_input.split('-'))) + cands = [] + for i in range(*space): + digs = str(i) + + if sorted(digs) != list(digs): + continue + if max(digs.count(d) for d in set(digs)) < 2: + continue + cands.append(i) + return cands + +def partI(cands): + return len(cands) + +def partII(cands): + c = 0 + for i in cands: + digs = str(i) + if 2 in [digs.count(d) for d in set(digs)]: + c += 1 + return c + diff --git a/day05.py b/day05.py new file mode 100644 index 0000000..27f676f --- /dev/null +++ b/day05.py @@ -0,0 +1,11 @@ +from intcode import Interpreter, SingletonIO + +def preproc(puzzle_input): + return list(map(int, puzzle_input.split(','))) + +def partI(program): + return Interpreter(program, SingletonIO).run(1) + +def partII(program): + return Interpreter(program, SingletonIO).run(5) + diff --git a/day06.py b/day06.py new file mode 100644 index 0000000..98afb87 --- /dev/null +++ b/day06.py @@ -0,0 +1,44 @@ +from lib import Graph + +def preproc(puzzle_input): + graph = Graph() + for line in puzzle_input.split('\n'): + sun, planet = line.split(')') + graph.add_edge(sun, planet) + return graph + +def partI(graph): + return sum(w for node, w in graph.min_paths("COM").items()) + +def partII(graph): + return graph.min_path("SAN", "YOU") - 2 + +import solver + +tests = solver.Tests() +tests.add("""COM)B +B)C +C)D +D)E +E)F +B)G +G)H +D)I +E)J +J)K +K)L""", partI=42) + +tests.add("""COM)B +B)C +C)D +D)E +E)F +B)G +G)H +D)I +E)J +J)K +K)L +K)YOU +I)SAN""", partII=4) + diff --git a/day07.py b/day07.py new file mode 100644 index 0000000..b823eff --- /dev/null +++ b/day07.py @@ -0,0 +1,72 @@ +from lib import format_main +from intcode import Interpreter, makeIO, Singleton +from itertools import permutations + +stack_size = 5 +fst_amp_input = 0 + +IO = makeIO(list, Singleton) + +def preproc(puzzle_input): + program = list(map(int, puzzle_input.split(','))) + amp = lambda : Interpreter(program, IO) + return amp + + + +def partI(amp): + phase_range = range(stack_size) + best = 0 + + for perm in permutations(phase_range): + amp_in = fst_amp_input + for phase in perm: + amp_in = amp().run([phase, amp_in]) + best = max(amp_in, best) + return best + +def partII(amp): + phase_range = range(5, 10) + best = 0 + + for perm in permutations(phase_range): + amps = [amp() for _ in range(stack_size)] + for p, a in zip(perm, amps): + a.write([p]) + next(a) + + amp_in = fst_amp_input + while True: + try: + for a in amps: + a.write([amp_in]) + amp_in = next(a) + except StopIteration: + break + best = max(amp_in, best) + + return best + +import solver + +tests = solver.Tests() + +tests.add( + "3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0", partI=43210) + +tests.add( + "3,23,3,24,1002,24,10,24,1002,23,-1,23," + + "101,5,23,23,1,24,23,23,4,23,99,0,0", partI=54321) + +tests.add( + "3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33," + + "1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0", partI=65210) + +tests.add( + "3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26," + + "27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5", partII=139629729) + +tests.add( + "3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54," + + "-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4," + + "53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10", partII=18216) diff --git a/day08.py b/day08.py new file mode 100644 index 0000000..b12bec1 --- /dev/null +++ b/day08.py @@ -0,0 +1,48 @@ + +ZERO, ONE, TWO = "012" + +BLACK = 0 +WHITE = 1 + +color = {WHITE : u"\u2B1C", + BLACK : u"\u2B1B" + } + + +class Image: + def __init__(self, data, w, h): + self.data = data + self.w = w + self.h = h + + def layers(self): + w, h = self.w, self.h + return (self.data[i:i+w*h] for i in range(0, len(self.data), w*h)) + + def image(self): + final = str() + for stack in zip(*self.layers()): + final += (''.join(stack).replace(TWO, "") + TWO)[0] + return final + + def __repr__(self): + w, image = self.w, self.image() + return '\n'.join(image[i:i+w] for i in range(0, len(image), w)) + + def __str__(self): + return repr(self).replace(ZERO, color[BLACK]).replace(ONE, color[WHITE]) + + + +def preproc(puzzle_input): + w, h = 25, 6 + data = puzzle_input.replace('\n', '') + return Image(data, w, h) + +def partI(image): + layer = min(image.layers(), key=lambda ly: ly.count(ZERO)) + return layer.count(ONE) * layer.count(TWO) + +def partII(image): + return '\n' + str(image) + diff --git a/day09.py b/day09.py new file mode 100644 index 0000000..40b30c1 --- /dev/null +++ b/day09.py @@ -0,0 +1,15 @@ +from intcode import Interpreter, SingletonIO + +def preproc(puzzle_input): + return list(map(int, puzzle_input.split(','))) + +def partI(program): + return Interpreter(program, SingletonIO).run(1) + +def partII(program): + return Interpreter(program, SingletonIO).run(2) + +import solver + +tests = solver.Tests() +tests.add("104,1125899906842624,99", partI=1125899906842624) diff --git a/day10.py b/day10.py new file mode 100644 index 0000000..4f1abd5 --- /dev/null +++ b/day10.py @@ -0,0 +1,146 @@ +from fractions import gcd, Fraction +from itertools import product +from functools import total_ordering +from collections import defaultdict + +ASTEROID = '#' + +def zrange(x, y, step): + if x == y == step == 0: + def yzero(): + while True: + yield 0 + return yzero() + return range(x, y, step) + +def angle(v): + if v.x == 0: + return (2 * int(v.y > 0), 0) + halve = 1 if v.x > 0 else 3 + return (halve, Fraction(v.y, v.x)) + +def rebase(base, vectors): + return (v - base for v in vectors if v != base) + +@total_ordering +class Vector: + def __init__(self, x, y): + self.x = x + self.y = y + def __add__(self, other): + return Vector(self.x + other.x, self.y + other.y) + def __sub__(self, other): + return self + Vector(-other.x, -other.y) + def __eq__(self, other): + return self.x == other.x and self.y == other.y + def __lt__(self, other): + return (self.x**2 + self.y**2) < (other.x**2 + other.y**2) + def __str__(self): + return str((self.x, self.y)) + def __repr__(self): + return str(self) + +class Map: + def __init__(self, map): + self.map = map + + def __getitem__(self, v): + return self.map[v.y][v.x] + + def __str__(self): + return '\n'.join(self.map) + + def is_asteroid(self, v): + return self[v] == ASTEROID + + def vectors(self): + X, Y = len(self.map[0]), len(self.map) + return (Vector(x, y) for x, y in product(range(X), range(Y))) + + def asteroid_vectors(self): + return filter(lambda v: self.is_asteroid(v), self.vectors()) + + def num_in_sight(self, v): + vs = rebase(v, self.asteroid_vectors()) + return len(set(map(angle, vs))) + + def best(self): + return max((self.num_in_sight(v), v) for v in self.asteroid_vectors()) + + def vaporize_seq(self, base): + vs = rebase(base, self.asteroid_vectors()) + angles = defaultdict(list) + for v in vs: + angles[angle(v)].append(v) + for ang in angles.keys(): + angles[ang].sort() + r = [] + for i in range(max(map(len, angles.values()))): + r += [angles[ang][i] + base + for ang in sorted(angles.keys()) if i < len(angles[ang])] + return r + +def preproc(puzzle_input): + asteroid_map = Map(puzzle_input.split()) + score, asteroid = asteroid_map.best() + return asteroid_map, asteroid, score + +def partI(packed): + _, _, score = packed + return score + +def partII(packed): + asteroid_map, asteroid, _ = packed + v = asteroid_map.vaporize_seq(asteroid)[199] + return 100*v.x + v.y + +import solver + +tests = solver.Tests() + +map1 = \ +""".#..# +..... +##### +....# +...##""" + +map2 = \ +"""#.#...#.#. +.###....#. +.#....#... +##.#.#.#.# +....#.#.#. +.##..###.# +..#...##.. +..##....## +......#... +.####.###. +""" + +map3 = \ +""".#..##.###...####### +##.############..##. +.#.######.########.# +.###.#######.####.#. +#####.##.#.##.###.## +..#####..#.######### +#################### +#.####....###.#.#.## +##.################# +#####.##.###..####.. +..######..##.####### +####.##.####...##..# +.#####..#.######.### +##...#.##########... +#.##########.####### +.####.#.###.###.#.## +....##.##.###..##### +.#.#.###########.### +#.#.#.#####.####.### +###.##.####.##.#..## +""" + +tests.add(map1, partI=8) +tests.add(map2, partI=35) +tests.add(map3, partI=210, partII=802) diff --git a/day11.py b/day11.py new file mode 100644 index 0000000..c433cb9 --- /dev/null +++ b/day11.py @@ -0,0 +1,85 @@ +from advent import read_input +from intcode import Interpreter +from collections import defaultdict + +BLACK = 0 +WHITE = 1 + +color = {WHITE : u"\u2B1C", + BLACK : u"\u2B1B" + } + +UP = (0, 1) +LEFT = 0 +RIGHT = 1 + +def rotate(v, dir): + return (-dir) * v[1], dir * v[0] + +def left(v): + return rotate(v, 1) + +def right(v): + return rotate(v, -1) + +def add(v1, v2): + return v1[0] + v2[0], v1[1] + v2[1] + +class Canvas(defaultdict): + def __init__(self, color): + self.color = color + super(Canvas, self).__init__(int) + + def __str__(self): + xs, ys = zip(*self.keys()) + return '\n'.join(''.join(self.color[self[(x, y)]] + for x in range(min(xs), max(xs)+1)) + for y in range(max(ys), min(ys)-1, -1)) + + def __repr__(self): + return unicode(self).encode("utf-8") + + +class robotIO: + def __init__(self, canvas=Canvas(color)): + self.canvas = canvas + self.dir = UP + self.pos = 0, 0 + self.paint = True + + def pop_input(self): + return self.canvas[self.pos] + + def append_output(self, x): + if self.paint: + self.canvas[self.pos] = x + else: + self.dir = left(self.dir) if x == LEFT else right(self.dir) + self.pos = add(self.pos, self.dir) + self.paint = not self.paint + + def flush(self): + return self.canvas + +class Robot: + def __init__(self, program, canvas=Canvas(color)): + self.canvas = canvas + self.program = program + + def paint(self): + comp = Interpreter(self.program, robotIO) + comp.IO.canvas = self.canvas + return comp.run() + +def preproc(puzzle_input): + return list(map(int, puzzle_input.split(','))) + +def partI(program): + canvas = Robot(program).paint() + return len(canvas) + +def partII(program): + canvas = Canvas(color) + canvas[(0, 0)] = WHITE + canvas = Robot(program, canvas).paint() + return '\n' + str(canvas)