@@ -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) |