| @@ -4,3 +4,4 @@ __pycache__/ | |||
| .DS_Store | |||
| venv/ | |||
| .env | |||
| *.js | |||
| @@ -1,23 +1,19 @@ | |||
| include .env | |||
| FILE = $(shell find . -path "./y????/p??.py" -type f | xargs ls -rt | tail -n 1) | |||
| DATA = $(shell echo $(FILE) | sed -e s/\.py/\.dat/) | |||
| PYTHONPATH = . | |||
| export | |||
| main: venv/ $(DATA) | |||
| @cat $(DATA) | venv/bin/python -u $(FILE) | |||
| venv/bin/python -u toolkit.py $(FILE) | |||
| flake: venv/ | |||
| venv/bin/flake8 --exclude=venv/ | |||
| $(DATA): | |||
| @echo $(DATA) | venv/bin/python -c "import toolkit; toolkit.get_dat()" $(DATA) | |||
| venv/: requirements.txt | |||
| rm -rf venv/ | |||
| ~/.pyenv/versions/3.8.0/bin/python -m venv venv | |||
| venv/bin/pip install -r requirements.txt | |||
| touch requirements.txt venv/ | |||
| # install flake8 git hook | |||
| echo 'venv/bin/flake8 --exclude=venv/' > .git/hooks/pre-commit | |||
| chmod +x .git/hooks/pre-commit | |||
| # echo 'venv/bin/flake8 --exclude=venv/' > .git/hooks/pre-commit | |||
| # chmod +x .git/hooks/pre-commit | |||
| @@ -1,9 +1,12 @@ | |||
| import builtins | |||
| import collections | |||
| import hashlib | |||
| import itertools | |||
| import importlib | |||
| import math | |||
| import os | |||
| import re | |||
| import string | |||
| import sys | |||
| from pathlib import Path | |||
| @@ -64,14 +67,14 @@ def shortest_path(start, end, move): | |||
| return path[::-1] | |||
| def get_dat(): | |||
| path = sys.argv[1] | |||
| year, qn = map(int, re.findall(r'\d+', sys.argv[1])) | |||
| url = f'https://adventofcode.com/{year}/day/{qn}/input' | |||
| cookies = {'session': os.environ['SESSION']} | |||
| response = requests.get(url, cookies=cookies) | |||
| response.raise_for_status() | |||
| Path(path).write_bytes(response.content) | |||
| def ensure_data(path): | |||
| if not path.exists(): | |||
| year, qn = map(int, re.findall(r'\d+', sys.argv[1])) | |||
| url = f'https://adventofcode.com/{year}/day/{qn}/input' | |||
| cookies = {'session': os.environ['SESSION']} | |||
| response = requests.get(url, cookies=cookies) | |||
| response.raise_for_status() | |||
| path.write_bytes(response.content) | |||
| def batch(iterable, size): | |||
| @@ -97,3 +100,15 @@ def loop_consume(lines, handler): | |||
| count += 1 | |||
| else: | |||
| raise RuntimeError('Reached steady state') | |||
| if __name__ == '__main__': | |||
| data_file = Path(sys.argv[1]).with_suffix('.dat') | |||
| ensure_data(data_file) | |||
| builtins.data_file = data_file | |||
| builtins.string = string | |||
| builtins.re = re | |||
| rel = re.sub(r'.+(y\d+)/(p\d+).+', r'\1.\2', os.environ['FILE']) | |||
| mod = importlib.import_module(rel) | |||
| print(getattr(mod, 'ans1', None)) | |||
| print(getattr(mod, 'ans2', None)) | |||
| @@ -1,11 +1,6 @@ | |||
| import sys | |||
| ans1 = 0 | |||
| ans2 = None | |||
| for i, char in enumerate(sys.stdin.read(), 1): | |||
| for i, char in enumerate(data_file.read_text(), 1): | |||
| ans1 += {'(': 1, ')': -1}[char] | |||
| if ans1 == -1 and ans2 is None: | |||
| ans2 = i | |||
| print(ans1) | |||
| print(ans2) | |||
| @@ -1,11 +1,6 @@ | |||
| import sys | |||
| ans1 = 0 | |||
| ans2 = 0 | |||
| for line in sys.stdin.readlines(): | |||
| for line in data_file.read_text().splitlines(): | |||
| a, b, c = sorted(map(int, line.split('x'))) | |||
| ans1 += 2 * (a * b + b * c + a * c) + a * b | |||
| ans2 += a * b * c + 2 * (a + b) | |||
| print(ans1) | |||
| print(ans2) | |||
| @@ -1,12 +1,7 @@ | |||
| import sys | |||
| from itertools import accumulate as acc | |||
| text = sys.stdin.read() | |||
| text = data_file.read_text() | |||
| dirs = dict(zip('<>^v', [-1, 1, -1j, 1j])) | |||
| ans1 = len({*acc(map(dirs.get, text))}) | |||
| print(ans1) | |||
| ans2 = len({*acc(map(dirs.get, text[::2])), *acc(map(dirs.get, text[1::2]))}) | |||
| print(ans2) | |||
| @@ -1,4 +1,3 @@ | |||
| import sys | |||
| from hashlib import md5 | |||
| from multiprocessing import Pool | |||
| @@ -9,15 +8,13 @@ def mine(code, i): | |||
| return i, hasher.hexdigest() | |||
| if __name__ == '__main__': | |||
| code = sys.stdin.read().strip() | |||
| ans1 = None | |||
| ans2 = None | |||
| with Pool() as pool: | |||
| for i, coin in pool.starmap(mine, [(code, i) for i in range(10**7)]): | |||
| if ans1 is None and coin.startswith('00000'): | |||
| ans1 = i | |||
| if ans2 is None and coin.startswith('000000'): | |||
| ans2 = i | |||
| print(ans1) | |||
| print(ans2) | |||
| code = data_file.read_text().strip() | |||
| ans1 = None | |||
| ans2 = None | |||
| for i in range(10**7): | |||
| i, coin = mine(code, i) | |||
| if ans1 is None and coin.startswith('00000'): | |||
| ans1 = i | |||
| if ans2 is None and coin.startswith('000000'): | |||
| ans2 = i | |||
| break | |||
| @@ -1,7 +1,3 @@ | |||
| import re | |||
| import sys | |||
| def checks1(string): | |||
| yield len(re.findall(r'([aeiou])', string)) >= 3 | |||
| yield len(re.findall(r'(.)\1', string)) | |||
| @@ -15,8 +11,6 @@ def checks2(string): | |||
| ans1 = 0 | |||
| ans2 = 0 | |||
| for line in sys.stdin.read().splitlines(): | |||
| for line in data_file.read_text().splitlines(): | |||
| ans1 += all(checks1(line)) | |||
| ans2 += all(checks2(line)) | |||
| print(ans1) | |||
| print(ans2) | |||
| @@ -0,0 +1,29 @@ | |||
| def pythonize(string): | |||
| op, out = ( | |||
| re.sub(r'([a-z]+)', r'\1_', string) | |||
| .replace('AND', '&') | |||
| .replace('OR', '|') | |||
| .replace('NOT ', '~') | |||
| .replace('RSHIFT', '>>') | |||
| .replace('LSHIFT', '<<') | |||
| ).split(' -> ') | |||
| return f'{out} = {op}' | |||
| def process(instructions, registers={}): | |||
| while instructions: | |||
| curr, *instructions = instructions | |||
| out = curr.split()[0] | |||
| if out in registers: | |||
| continue | |||
| try: | |||
| exec(curr, None, registers) | |||
| except NameError: | |||
| instructions.append(curr) | |||
| return registers | |||
| inp = [pythonize(string) for string in data_file.read_text().splitlines()] | |||
| found = process(inp.copy()) | |||
| ans1 = found['a_'] | |||
| ans2 = process(inp.copy(), {'b_': ans1})['a_'] | |||
| @@ -0,0 +1,49 @@ | |||
| import sys | |||
| from itertools import permutations | |||
| text = sys.stdin.read() | |||
| # transform text into data structures | |||
| valid = set() | |||
| target = {} | |||
| for y, line in enumerate(text.splitlines()): | |||
| for x, char in enumerate(line): | |||
| if char == '#': | |||
| continue | |||
| valid.add(x + 1j * y) | |||
| if char != '.': | |||
| target[x + 1j * y] = char | |||
| # generate travel map | |||
| graph = {} | |||
| for A in target: | |||
| seen = {A} | |||
| boundary = {A} | |||
| pending = set(target) - seen | |||
| N = 0 | |||
| while pending: | |||
| N += 1 | |||
| boundary = {pos + step for pos in boundary for step in [1, -1, 1j, -1j]} | |||
| boundary &= valid - seen | |||
| seen.update(boundary) | |||
| for B in boundary & pending: | |||
| pending -= {B} | |||
| graph[target[A], target[B]] = N | |||
| # use map to determine routes | |||
| calc = lambda combo: sum(graph[pair] for pair in zip(combo, combo[1:])) | |||
| z = '0' | |||
| rest = set(target.values()) - {z} | |||
| options = [(z,) + combo for combo in permutations(rest)] | |||
| ans = min(options, key=calc) | |||
| print(calc(ans)) | |||
| options = [(z,) + combo + (z,) for combo in permutations(rest)] | |||
| ans = min(options, key=calc) | |||
| print(calc(ans)) | |||
| @@ -0,0 +1,15 @@ | |||
| inp = data_file.read_text().strip() | |||
| out = {} | |||
| for a in [''] + list(string.ascii_lowercase): | |||
| text = inp.replace(a, '').replace(a.swapcase(), '') | |||
| stack1 = list(text) | |||
| stack2 = [] | |||
| while stack1: | |||
| x = stack1.pop() | |||
| if stack2 and x.swapcase() == stack2[-1]: | |||
| y = stack2.pop() | |||
| else: | |||
| stack2.append(x) | |||
| out[a] = len(stack2) | |||
| ans1 = out[''] | |||
| ans2 = min(out.values()) | |||
| @@ -0,0 +1,50 @@ | |||
| import collections | |||
| import itertools | |||
| import re | |||
| import sys | |||
| txt = '''90342 ;2 correct | |||
| 70794 ;0 correct | |||
| 39458 ;2 correct | |||
| 34109 ;1 correct | |||
| 51545 ;2 correct | |||
| 12531 ;1 correct | |||
| ''' # 39542 is unique | |||
| txt = sys.stdin.read() | |||
| exclusions = collections.defaultdict(set) | |||
| clues = [] | |||
| for string, n in re.findall(r'(\d+) ;(\d)', txt): | |||
| choices = set(enumerate(string)) | |||
| combos = [frozenset(cs) for cs in itertools.combinations(choices, int(n))] | |||
| for combo in combos: | |||
| exclusions[combo] |= (choices - combo) | |||
| if int(n): | |||
| clues.append(combos) | |||
| for pos in range(len(string)): | |||
| choices = {(pos, char) for char in '0123456789'} | |||
| for choice in choices: | |||
| exclusions[frozenset({choice})] |= (choices - {choice}) | |||
| def solve(clues_left, known=frozenset()): | |||
| if not clues_left: | |||
| avail = {a for gr in exclusions for a in gr} | |||
| avail -= {v for k, vs in exclusions.items() if k <= known for v in vs} | |||
| yield ''.join(c for _, c in sorted(avail)) | |||
| else: | |||
| choices = min(clues_left, key=len) | |||
| for choice in choices: | |||
| chosen = known | choice | |||
| bads = {v for k, vs in exclusions.items() if k <= chosen for v in vs} | |||
| mods = [[c for c in cl if not c & bads] for cl in clues_left if cl != choices] | |||
| yield from solve(mods, chosen) | |||
| solution = next(solve(clues)) | |||
| print(solution) | |||