Continue with 2016 and 2017.
This commit is contained in:
57
2016/d14.py
Normal file
57
2016/d14.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import hashlib
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Candidate:
|
||||
char: str
|
||||
age: int
|
||||
orig_hash: int
|
||||
conf_hash: int = 0
|
||||
|
||||
data = "cuanljph"
|
||||
# data = "abc"
|
||||
|
||||
keys = []
|
||||
candidates = []
|
||||
rehashs = 2016 # part 2
|
||||
rehashs = 0 # part 1
|
||||
|
||||
for hash_idx in range(0, 100_000):
|
||||
h = hashlib.md5(f"{data}{hash_idx}".encode()).hexdigest()
|
||||
for _ in range(rehashs):
|
||||
h = hashlib.md5(h.encode()).hexdigest()
|
||||
|
||||
idx3 = None
|
||||
for i in range(len(h) - 3):
|
||||
if all([h[i] == h[i + j] for j in range(3)]):
|
||||
idx3 = i
|
||||
break
|
||||
|
||||
if idx3 is not None:
|
||||
candidates.append(Candidate(h[idx3], 0, hash_idx))
|
||||
|
||||
letters = []
|
||||
for i in range(len(h) - 5):
|
||||
if all([h[i] == h[i + j] for j in range(5)]):
|
||||
letters.append(h[i])
|
||||
|
||||
for current_char in set(letters):
|
||||
for c in candidates:
|
||||
if c.char == current_char and c.age > 0:
|
||||
# print(f"Confirmed {c} at {hash_idx}.")
|
||||
keys.append(c)
|
||||
c.age = 1000 # mark for removal from candidates
|
||||
c.conf_hash = hash_idx
|
||||
|
||||
if len(keys) >= 64:
|
||||
# print("done")
|
||||
break
|
||||
|
||||
# remove old candidates
|
||||
candidates = [c for c in candidates if c.age < 1000]
|
||||
for c in candidates:
|
||||
c.age += 1
|
||||
|
||||
keys.sort(key=lambda c: c.orig_hash)
|
||||
print(keys[63].orig_hash)
|
||||
45
2016/d15.py
Normal file
45
2016/d15.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import sys
|
||||
from lib import str_to_ints
|
||||
from copy import deepcopy
|
||||
|
||||
with open("i15.txt") as f:
|
||||
d = f.read()
|
||||
|
||||
part_2 = True
|
||||
if part_2:
|
||||
d += "#7 11 0 0"
|
||||
|
||||
discs = []
|
||||
for line in d.splitlines():
|
||||
id, npos, time, cpos = str_to_ints(line)
|
||||
discs.append([cpos, npos])
|
||||
|
||||
qpos = -1
|
||||
orig_discs = deepcopy(discs)
|
||||
|
||||
# empirically could dev algo to find or CRT?
|
||||
if part_2:
|
||||
start, delta = 12136, 33915
|
||||
else:
|
||||
start, delta = 831, 2261
|
||||
|
||||
for start_time in range(start, 1_000_000_000, delta):
|
||||
discs = deepcopy(orig_discs)
|
||||
|
||||
# let discs move for initial wait
|
||||
for _ in range(start_time):
|
||||
for disc in discs:
|
||||
disc[0] = (disc[0] + 1) % disc[1]
|
||||
|
||||
# drop the capsule
|
||||
for i in range(len(discs)):
|
||||
# discs move first
|
||||
for disc in discs:
|
||||
disc[0] = (disc[0] + 1) % disc[1]
|
||||
|
||||
# check if there is a collission for the current disc
|
||||
if discs[i][0] != 0:
|
||||
break
|
||||
else:
|
||||
print(start_time)
|
||||
sys.exit(0)
|
||||
26
2017/d2.py
Normal file
26
2017/d2.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from lib import *
|
||||
|
||||
data = open(0).read().strip()
|
||||
|
||||
|
||||
m = list(map(str_to_ints, data.splitlines()))
|
||||
|
||||
s = 0
|
||||
for row in m:
|
||||
s += (max(row) - min(row))
|
||||
|
||||
print(s)
|
||||
|
||||
|
||||
s = 0
|
||||
for row in m:
|
||||
|
||||
for i in range(len(row)):
|
||||
for j in range(i + 1, len(row)):
|
||||
if row[i] % row[j] == 0:
|
||||
s += row[i] // row[j]
|
||||
if row[j] % row[i] == 0:
|
||||
s += row[j] // row[i]
|
||||
|
||||
print(s)
|
||||
|
||||
98
2017/d3.py
Normal file
98
2017/d3.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import sys
|
||||
import lib
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
data = open(0).read().strip()
|
||||
|
||||
|
||||
def more_efficient_part_1(data):
|
||||
def corners():
|
||||
layer = 0
|
||||
corner = 1
|
||||
edge_len = 3
|
||||
yield (corner, layer, edge_len)
|
||||
while True:
|
||||
corner += (edge_len - 1) * 4
|
||||
layer += 1
|
||||
yield (corner, layer, edge_len)
|
||||
edge_len += 2
|
||||
|
||||
square = int(data)
|
||||
corner, layer, edge_len = 0, 0, 0
|
||||
for corner, layer, edge_len in corners():
|
||||
if square < corner:
|
||||
break
|
||||
|
||||
coord = (layer, layer)
|
||||
while corner != square:
|
||||
for dir in [(0, -1), (-1, 0), (0, 1), (1, 0)]:
|
||||
for _ in range(edge_len - 1):
|
||||
corner -= 1
|
||||
coord = lib.add2(coord, dir)
|
||||
if corner == square:
|
||||
break
|
||||
if corner == square:
|
||||
break
|
||||
return sum(map(abs, coord))
|
||||
|
||||
|
||||
|
||||
def walk_spiral():
|
||||
yield (0, 0), 1
|
||||
yield (0, 1), 2
|
||||
|
||||
value = 2
|
||||
coord = (0, 1)
|
||||
step_len = 1
|
||||
|
||||
up = (-1, 0)
|
||||
left = (0, -1)
|
||||
down = (1, 0)
|
||||
right = (0, 1)
|
||||
|
||||
|
||||
while True:
|
||||
for _ in range(step_len):
|
||||
coord = lib.add2(coord, up)
|
||||
value += 1
|
||||
yield coord, value
|
||||
|
||||
step_len += 1
|
||||
|
||||
for _ in range(step_len):
|
||||
coord = lib.add2(coord, left)
|
||||
value += 1
|
||||
yield coord, value
|
||||
|
||||
for _ in range(step_len):
|
||||
coord = lib.add2(coord, down)
|
||||
value += 1
|
||||
yield coord, value
|
||||
|
||||
step_len += 1
|
||||
for _ in range(step_len):
|
||||
coord = lib.add2(coord, right)
|
||||
value += 1
|
||||
yield coord, value
|
||||
|
||||
values = defaultdict(int)
|
||||
values[(0, 0)] = 1
|
||||
part_1 = False
|
||||
|
||||
for coord, value in walk_spiral():
|
||||
if part_1 and value == int(data):
|
||||
print(sum(map(abs, coord)))
|
||||
break
|
||||
|
||||
nval = 0
|
||||
for d in lib.nbs8:
|
||||
nb = lib.add2(coord, d)
|
||||
nval += values[nb]
|
||||
|
||||
if nval > 0:
|
||||
values[coord] = nval
|
||||
|
||||
if nval > int(data):
|
||||
print(nval)
|
||||
break
|
||||
244
2017/lib.py
Normal file
244
2017/lib.py
Normal file
@@ -0,0 +1,244 @@
|
||||
import re
|
||||
import os
|
||||
import string
|
||||
import heapq
|
||||
|
||||
NUMBERS = string.digits
|
||||
LETTERS_LOWER = string.ascii_lowercase
|
||||
LETTERS_UPPER = string.ascii_uppercase
|
||||
|
||||
INF = float("inf")
|
||||
fst = lambda l: l[0]
|
||||
snd = lambda l: l[1]
|
||||
nbs4 = [(-1, 0), (0, 1), (1, 0), (0, -1)]
|
||||
nbs8 = [(-1, 0), (0, 1), (1, 0), (0, -1), (-1, -1), (-1, 1), (1, 1), (1, -1)]
|
||||
|
||||
def nth(n):
|
||||
return lambda l: l[n]
|
||||
|
||||
def maps(f, xs):
|
||||
if isinstance(xs, list):
|
||||
return [maps(f, x) for x in xs]
|
||||
return f(xs)
|
||||
|
||||
def mape(f, xs):
|
||||
return list(map(f, xs))
|
||||
|
||||
def add2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]:
|
||||
return (a[0] + b[0], a[1] + b[1])
|
||||
|
||||
class Grid2D:
|
||||
N = (-1, 0)
|
||||
E = (0, 1)
|
||||
S = (1, 0)
|
||||
W = (0, -1)
|
||||
NW = (-1, -1)
|
||||
NE = (-1, 1)
|
||||
SE = (1, 1)
|
||||
SW = (1, -1)
|
||||
COORDS_ORTH = (N, E, S, W)
|
||||
COORDS_DIAG = (NW, NE, SE, SW)
|
||||
|
||||
def __init__(self, text: str):
|
||||
lines = [line for line in text.splitlines() if line.strip() != ""]
|
||||
self.grid = list(map(list, lines))
|
||||
self.n_rows = len(self.grid)
|
||||
self.n_cols = len(self.grid[0])
|
||||
|
||||
def __getitem__(self, pos: tuple[int, int]):
|
||||
row, col = pos
|
||||
return self.grid[row][col]
|
||||
|
||||
def __setitem__(self, pos: tuple[int, int], val):
|
||||
row, col = pos
|
||||
self.grid[row][col] = val
|
||||
|
||||
def clone_with_val(self, val):
|
||||
c = Grid2D("d\nd")
|
||||
c.n_rows = self.n_rows
|
||||
c.n_cols = self.n_cols
|
||||
c.grid = [[val for _ in range(c.n_cols)]
|
||||
for _ in range(self.n_rows)]
|
||||
return c
|
||||
|
||||
def rows(self) -> list[list[str]]:
|
||||
return [row for row in self.grid]
|
||||
|
||||
def cols(self) -> list[list[str]]:
|
||||
rows = self.rows()
|
||||
return [[row[col_i] for row in rows]
|
||||
for col_i in range(self.n_cols)]
|
||||
|
||||
def find(self, chars: str) -> list[tuple[int, int]]:
|
||||
return [c for c in self.all_coords() if self[c] in chars]
|
||||
|
||||
def find_not(self, chars: str) -> list[tuple[int, int]]:
|
||||
return [c for c in self.all_coords() if self[c] not in chars]
|
||||
|
||||
def all_coords(self) -> list[tuple[int, int]]:
|
||||
return [(row_i, col_i)
|
||||
for row_i in range(self.n_rows)
|
||||
for col_i in range(self.n_cols)]
|
||||
|
||||
def row_coords(self, row_i) -> list[tuple[int, int]]:
|
||||
assert row_i < self.n_rows, f"{row_i=} must be smaller than {self.n_rows=}"
|
||||
return [(col_i, row_i) for col_i in range(self.n_cols)]
|
||||
|
||||
def col_coords(self, col_i) -> list[tuple[int, int]]:
|
||||
assert col_i < self.n_cols, f"{col_i=} must be smaller than {self.n_cols=}"
|
||||
return [(col_i, row_i) for row_i in range(self.n_rows)]
|
||||
|
||||
def contains(self, pos: tuple[int, int]) -> bool:
|
||||
row, col = pos
|
||||
return row >= 0 and row < self.n_rows and col >= 0 and col < self.n_cols
|
||||
|
||||
def __contains__(self, pos: tuple[int, int]) -> bool:
|
||||
return self.contains(pos)
|
||||
|
||||
def neighbors_ort(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
|
||||
return [add2(pos, off) for off in self.dirs_ort() if self.contains(add2(pos, off))]
|
||||
|
||||
def neighbors_vert(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
|
||||
return [add2(pos, off) for off in self.dirs_vert() if self.contains(add2(pos, off))]
|
||||
|
||||
def neighbors_adj(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
|
||||
return self.neighbors_ort(pos) + self.neighbors_vert(pos)
|
||||
|
||||
def flip_ort(self, pos: tuple[int, int]) -> tuple[int, int]:
|
||||
return (-pos[0], -pos[1])
|
||||
|
||||
def dirs_ort(self) -> list[tuple[int, int]]:
|
||||
return [self.N, self.E, self.S, self.W]
|
||||
|
||||
def dirs_vert(self) -> list[tuple[int, int]]:
|
||||
return [self.NE, self.SE, self.SW, self.NW]
|
||||
|
||||
def print(self):
|
||||
for r in self.rows():
|
||||
print("".join(r))
|
||||
|
||||
def print_with_gaps(self):
|
||||
for r in self.rows():
|
||||
print(" ".join(map(str, r)))
|
||||
|
||||
class Input:
|
||||
def __init__(self, text: str):
|
||||
if os.path.isfile(text):
|
||||
self.text = open(text).read()
|
||||
else:
|
||||
self.text = text
|
||||
|
||||
def stats(self):
|
||||
print(f" size: {len(self.text)}")
|
||||
print(f"lines: {len(self.text.splitlines())}")
|
||||
ps = len(self.paras())
|
||||
print(f"paras: {ps}")
|
||||
|
||||
def lines(self) -> list[str]:
|
||||
return self.text.splitlines()
|
||||
|
||||
def paras(self) -> list[str]:
|
||||
return [p for p in self.text.split("\n\n")]
|
||||
|
||||
def grid2(self) -> Grid2D:
|
||||
return Grid2D(self.text)
|
||||
|
||||
def prime_factors(n):
|
||||
"""
|
||||
Returns a list of prime factors for n.
|
||||
|
||||
:param n: number for which prime factors should be returned
|
||||
"""
|
||||
factors = []
|
||||
rest = n
|
||||
divisor = 2
|
||||
while rest % divisor == 0:
|
||||
factors.append(divisor)
|
||||
rest //= divisor
|
||||
divisor = 3
|
||||
while divisor * divisor <= rest:
|
||||
while rest % divisor == 0:
|
||||
factors.append(divisor)
|
||||
rest //= divisor
|
||||
divisor += 2
|
||||
if rest != 1:
|
||||
factors.append(rest)
|
||||
return factors
|
||||
|
||||
def lcm(numbers: list[int]) -> int:
|
||||
fs = []
|
||||
for n in numbers:
|
||||
fs += prime_factors(n)
|
||||
s = 1
|
||||
fs = list(set(fs))
|
||||
for f in fs:
|
||||
s *= f
|
||||
return s
|
||||
|
||||
def str_to_int(line: str) -> int:
|
||||
line = line.replace(" ", "")
|
||||
r = re.compile(r"(-?\d+)")
|
||||
m = r.findall(line)
|
||||
(x,) = m
|
||||
return int(x)
|
||||
|
||||
def str_to_ints(line: str) -> list[int]:
|
||||
r = re.compile(r"-?\d+")
|
||||
return list(map(int, r.findall(line)))
|
||||
|
||||
def str_to_lines_no_empty(text: str) -> list[str]:
|
||||
return list(filter(lambda l: l.strip() != "", text.splitlines()))
|
||||
|
||||
def str_to_lines(text: str) -> list[str]:
|
||||
return list(text.splitlines())
|
||||
|
||||
def count_trailing_repeats(lst):
|
||||
count = 0
|
||||
for elem in reversed(lst):
|
||||
if elem != lst[-1]:
|
||||
break
|
||||
else:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
class A_Star(object):
|
||||
def __init__(self, starts, is_goal, h, d, neighbors):
|
||||
"""
|
||||
:param h: heuristic function
|
||||
:param d: cost from node to node function
|
||||
:param neighbors: neighbors function
|
||||
"""
|
||||
open_set = []
|
||||
g_score = {}
|
||||
|
||||
for start in starts:
|
||||
heapq.heappush(open_set, (h(start), start))
|
||||
g_score[start] = d(0, start)
|
||||
|
||||
while open_set:
|
||||
current_f_score, current = heapq.heappop(open_set)
|
||||
if is_goal(current):
|
||||
self.cost = current_f_score
|
||||
break
|
||||
|
||||
for neighbor in neighbors(current):
|
||||
tentative_g_score = g_score[current] + d(current, neighbor)
|
||||
if neighbor not in g_score or \
|
||||
tentative_g_score < g_score[neighbor]:
|
||||
g_score[neighbor] = tentative_g_score
|
||||
f_score = g_score[neighbor] + h(neighbor)
|
||||
heapq.heappush(open_set, (f_score, neighbor))
|
||||
|
||||
def shoelace_area(corners):
|
||||
n = len(corners)
|
||||
area = 0
|
||||
for i in range(n):
|
||||
x1, y1 = corners[i]
|
||||
x2, y2 = corners[(i + 1) % n]
|
||||
area += (x1 * y2) - (x2 * y1)
|
||||
return abs(area) / 2.0
|
||||
|
||||
def extract_year_and_date(scriptname) -> tuple[str, str]:
|
||||
r = re.compile(r"aoc(\d\d\d\d)/d(\d+).py")
|
||||
[(year, day)] = r.findall(scriptname)
|
||||
return (year, day)
|
||||
Reference in New Issue
Block a user