from utils import cmap, compose, invoker, unpack, at, reduceUntil from functools import partial, reduce from itertools import permutations, chain from aoc.intcode import Runner parse = compose(list, cmap(int), invoker('split', ',')) # runs Intcode several times by piping their input/output with [phase[index], previousOutput] def runSequence(sequence, intcode, endCondition, startSignal=0): runners = [Runner(iter([phase])) for phase in sequence] runnerIterators = [runner.output_iterator(intcode) for runner in runners] # pipe last signal to current runner's input and add its next output signal to signals def step(signals, index): runners[index].feed(signals[-1]) signal = next(runnerIterators[index], None) return signals + [signal], (index + 1) % len(runners) return reduceUntil(endCondition, unpack(step), ([startSignal], 0))[0] oneLoop = unpack(lambda outputs, index: index == 0 and len(outputs) > 1) noMoreValues = unpack(lambda outputs, index: outputs[-1] is None) # -1 to get last in part A; -2 to get last in part B, since B stops on last = None (no more values) one = compose( max, cmap(at(-1)), lambda intcode: [ runSequence(sequence, intcode, oneLoop) for sequence in permutations(range(0, 5)) ], parse) two = compose(
from utils import cmap, compose, invoker, unpack, chunks from functools import partial import re N = 25 M = 6 L = N * M BLACK = '0' WHITE = '1' TRANSPARENT = '2' parse = partial(chunks, L) isOpaque = lambda value: value != TRANSPARENT showImage = lambda layer: '\n'.join(row for row in (''.join(layer[i:i + N]) for i in range(0, L, N))) # sub 0s for whitespaces and add a space between columns for readability makeReadable = compose(partial(re.sub, r'0', ' '), partial(re.sub, r'(0|1)', r'\g<1> ')) one = compose(lambda layer: layer.count('1') * layer.count('2'), partial(min, key=invoker('count', '0')), parse) two = compose(makeReadable, showImage, list, cmap(compose(next, partial(filter, isOpaque))), unpack(zip), parse)
# use a custom generator to efficiently skip ranges of numbers that contain decreasing sequences def generator(lower, upper): current = lower while current <= upper: index = next((i for i, n in enumerate(current[:-1]) if current[i] > current[i + 1]), None) if index is None: yield current current = str(int(current) + 1) else: current = current[:index + 1] + current[index] * len(current[index + 1:]) rulesA = [ lambda password: re.findall(r'(\d)\1', password), ] rulesB = [ lambda password: any( len(match) == 2 for match, char in re.findall(r'((\d)\2+)', password)), ] validator = lambda rules: lambda password: all( rule(password) for rule in rules) one = compose(len, list, partial(filter, validator(rulesA)), unpack(generator), invoker('split', '-')) two = compose(len, list, partial(filter, validator(rulesB)), unpack(generator), invoker('split', '-'))
from utils import cmap, compose, invoker, at, unpack, dictBuilder, switch, identity from text import patternGroups, toLines from functools import partial from itertools import product, permutations from space import Point import re CENTER = Point((0, 0)) # split input into lines, and each line (a wire) into a series of steps: [{'direction': 'U', 'length': 23}, {'direction' ....}] formatStep = compose(dictBuilder({'direction': str, 'length': int}), partial(patternGroups, r'([URDL])(\d+)')) formatWire = compose(cmap(formatStep), invoker('split', ',')) parseAll = compose(cmap(formatWire), toLines) # transforms a wire (a list of steps) into a list of segments def trace(wire): edges = [CENTER] for stretch in wire: edges.append(travel(stretch, edges[-1])) return list(zip(edges[:-1], edges[1:])) # given a wire stretch (e.g. U41) and a starting Point, returns the destination point def travel(stretch, origin): vector = switch(lambda direction, length: direction, { 'U': lambda direction, length: Point((0, length)), 'R': lambda direction, length: Point((length, 0)), 'D': lambda direction, length: Point((0, -length)), 'L': lambda direction, length: Point((-length, 0)), }) return Point(origin) + vector(**stretch)
from utils import cmap, compose, invoker, identity from text import toLines from functools import partial from collections import defaultdict ROOT = 'COM' parse = compose(list, cmap(invoker('split', ')')), toLines) def buildOrbits(pairs): orbits = defaultdict(list) for static, orbiter in pairs: orbits[static].append(orbiter) return orbits def countOrbits(source, orbits): def count(source, level=0): return level + sum( [count(orbiter, level + 1) for orbiter in orbits[source]]) return count(source) def path(target, orbits): def trace(current): orbiters = orbits[current[-1]] if len(orbiters) == 0: return current if current[-1] == target else None paths = (trace(current + [orbiter]) for orbiter in orbiters)