Solve 2018 day 16 and add function to download input

This commit is contained in:
felixm 2024-07-14 10:05:18 -04:00
parent db82589857
commit ab2e6f4ddb
3 changed files with 195 additions and 11 deletions

147
2018/d16.py Normal file
View File

@ -0,0 +1,147 @@
from lib import str_to_ints, get_data
def addr(regs, a, b, c):
regs[c] = regs[a] + regs[b]
def addi(regs, a, b, c):
regs[c] = regs[a] + b
def mulr(regs, a, b, c):
regs[c] = regs[a] * regs[b]
def muli(regs, a, b, c):
regs[c] = regs[a] * b
def banr(regs, a, b, c):
regs[c] = regs[a] & regs[b]
def bani(regs, a, b, c):
regs[c] = regs[a] & b
def borr(regs, a, b, c):
regs[c] = regs[a] | regs[b]
def bori(regs, a, b, c):
regs[c] = regs[a] | b
def setr(regs, a, _, c):
regs[c] = regs[a]
def seti(regs, a, _, c):
regs[c] = a
def gtir(regs, a, b, c):
regs[c] = 1 if a > regs[b] else 0
def gtri(regs, a, b, c):
regs[c] = 1 if regs[a] > b else 0
def gtrr(regs, a, b, c):
regs[c] = 1 if regs[a] > regs[b] else 0
def eqir(regs, a, b, c):
regs[c] = 1 if a == regs[b] else 0
def eqri(regs, a, b, c):
regs[c] = 1 if regs[a] == b else 0
def eqrr(regs, a, b, c):
regs[c] = 1 if regs[a] == regs[b] else 0
OPS = [
addr,
addi,
mulr,
muli,
banr,
bani,
borr,
bori,
setr,
seti,
gtir,
gtri,
gtrr,
eqir,
eqri,
eqrr,
]
def part_1(data):
examples = []
lines = data.splitlines()
test_prog = []
last_i = None
for i in range(len(lines)):
line = lines[i]
if line.startswith("Before:"):
regs = str_to_ints(line)
inst = str_to_ints(lines[i + 1])
regs_after = str_to_ints(lines[i + 2])
examples.append((regs, inst, regs_after))
last_i = i + 2
assert last_i is not None
for line in lines[last_i:]:
if line.strip() != "":
test_prog.append(str_to_ints(line))
r = 0
for before_orig, inst, after in examples:
ops_correct = []
for op in OPS:
before = list(before_orig)
op(before, *inst[1:])
if before == after:
ops_correct.append(op)
if len(ops_correct) >= 3:
r += 1
print(r)
code_to_op = {}
while len(OPS) > 0:
for before_orig, inst, after in examples:
ops_correct = []
for op in OPS:
before = list(before_orig)
op(before, *inst[1:])
if before == after:
ops_correct.append(op)
if len(ops_correct) == 1:
code_to_op[inst[0]] = ops_correct[0]
OPS.remove(ops_correct[0])
regs = [0, 0, 0, 0]
for line in test_prog:
op_code = line[0]
vals = line[1:]
code_to_op[op_code](regs, *vals)
print(regs[0])
def main():
data = get_data(__file__)
part_1(data)
if __name__ == "__main__":
main()

View File

@ -104,6 +104,7 @@ written in Python.
- Day 13: 73:09 - Day 13: 73:09
- Day 14: 20:48 - Day 14: 20:48
- Day 15: 185:11 - Day 15: 185:11
- Day 16: 36:10
# 2022 # 2022

58
lib.py
View File

@ -11,20 +11,25 @@ INF = float("inf")
fst = lambda l: l[0] fst = lambda l: l[0]
snd = lambda l: l[1] snd = lambda l: l[1]
def nth(n): def nth(n):
return lambda l: l[n] return lambda l: l[n]
def maps(f, xs): def maps(f, xs):
if isinstance(xs, list): if isinstance(xs, list):
return [maps(f, x) for x in xs] return [maps(f, x) for x in xs]
return f(xs) return f(xs)
def mape(f, xs): def mape(f, xs):
return list(map(f, xs)) return list(map(f, xs))
def add2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]: def add2(a: tuple[int, int], b: tuple[int, int]) -> tuple[int, int]:
return (a[0] + b[0], a[1] + b[1]) return (a[0] + b[0], a[1] + b[1])
class Grid2D: class Grid2D:
N = (-1, 0) N = (-1, 0)
E = (0, 1) E = (0, 1)
@ -55,8 +60,7 @@ class Grid2D:
c = Grid2D("d\nd") c = Grid2D("d\nd")
c.n_rows = self.n_rows c.n_rows = self.n_rows
c.n_cols = self.n_cols c.n_cols = self.n_cols
c.grid = [[val for _ in range(c.n_cols)] c.grid = [[val for _ in range(c.n_cols)] for _ in range(self.n_rows)]
for _ in range(self.n_rows)]
return c return c
def rows(self) -> list[list[str]]: def rows(self) -> list[list[str]]:
@ -64,8 +68,7 @@ class Grid2D:
def cols(self) -> list[list[str]]: def cols(self) -> list[list[str]]:
rows = self.rows() rows = self.rows()
return [[row[col_i] for row in rows] return [[row[col_i] for row in rows] for col_i in range(self.n_cols)]
for col_i in range(self.n_cols)]
def find(self, chars: str) -> list[tuple[int, int]]: def find(self, chars: str) -> list[tuple[int, int]]:
return [c for c in self.all_coords() if self[c] in chars] return [c for c in self.all_coords() if self[c] in chars]
@ -74,9 +77,11 @@ class Grid2D:
return [c for c in self.all_coords() if self[c] not in chars] return [c for c in self.all_coords() if self[c] not in chars]
def all_coords(self) -> list[tuple[int, int]]: def all_coords(self) -> list[tuple[int, int]]:
return [(row_i, col_i) return [
for row_i in range(self.n_rows) (row_i, col_i)
for col_i in range(self.n_cols)] 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]]: 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=}" assert row_i < self.n_rows, f"{row_i=} must be smaller than {self.n_rows=}"
@ -94,10 +99,14 @@ class Grid2D:
return self.contains(pos) return self.contains(pos)
def neighbors_ort(self, pos: tuple[int, int]) -> list[tuple[int, int]]: 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))] 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]]: 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))] 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]]: def neighbors_adj(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
return self.neighbors_ort(pos) + self.neighbors_vert(pos) return self.neighbors_ort(pos) + self.neighbors_vert(pos)
@ -119,6 +128,7 @@ class Grid2D:
for r in self.rows(): for r in self.rows():
print(" ".join(map(str, r))) print(" ".join(map(str, r)))
class Input: class Input:
def __init__(self, text: str): def __init__(self, text: str):
if os.path.isfile(text): if os.path.isfile(text):
@ -141,6 +151,7 @@ class Input:
def grid2(self) -> Grid2D: def grid2(self) -> Grid2D:
return Grid2D(self.text) return Grid2D(self.text)
def prime_factors(n): def prime_factors(n):
""" """
Returns a list of prime factors for n. Returns a list of prime factors for n.
@ -163,6 +174,7 @@ def prime_factors(n):
factors.append(rest) factors.append(rest)
return factors return factors
def lcm(numbers: list[int]) -> int: def lcm(numbers: list[int]) -> int:
fs = [] fs = []
for n in numbers: for n in numbers:
@ -173,6 +185,7 @@ def lcm(numbers: list[int]) -> int:
s *= f s *= f
return s return s
def str_to_int(line: str) -> int: def str_to_int(line: str) -> int:
line = line.replace(" ", "") line = line.replace(" ", "")
r = re.compile(r"(-?\d+)") r = re.compile(r"(-?\d+)")
@ -180,16 +193,20 @@ def str_to_int(line: str) -> int:
(x,) = m (x,) = m
return int(x) return int(x)
def str_to_ints(line: str) -> list[int]: def str_to_ints(line: str) -> list[int]:
r = re.compile(r"-?\d+") r = re.compile(r"-?\d+")
return list(map(int, r.findall(line))) return list(map(int, r.findall(line)))
def str_to_lines_no_empty(text: str) -> list[str]: def str_to_lines_no_empty(text: str) -> list[str]:
return list(filter(lambda l: l.strip() != "", text.splitlines())) return list(filter(lambda l: l.strip() != "", text.splitlines()))
def str_to_lines(text: str) -> list[str]: def str_to_lines(text: str) -> list[str]:
return list(text.splitlines()) return list(text.splitlines())
def count_trailing_repeats(lst): def count_trailing_repeats(lst):
count = 0 count = 0
for elem in reversed(lst): for elem in reversed(lst):
@ -199,6 +216,7 @@ def count_trailing_repeats(lst):
count += 1 count += 1
return count return count
class A_Star(object): class A_Star(object):
def __init__(self, starts, is_goal, h, d, neighbors): def __init__(self, starts, is_goal, h, d, neighbors):
""" """
@ -221,12 +239,12 @@ class A_Star(object):
for neighbor in neighbors(current): for neighbor in neighbors(current):
tentative_g_score = g_score[current] + d(current, neighbor) tentative_g_score = g_score[current] + d(current, neighbor)
if neighbor not in g_score or \ if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
tentative_g_score < g_score[neighbor]:
g_score[neighbor] = tentative_g_score g_score[neighbor] = tentative_g_score
f_score = g_score[neighbor] + h(neighbor) f_score = g_score[neighbor] + h(neighbor)
heapq.heappush(open_set, (f_score, neighbor)) heapq.heappush(open_set, (f_score, neighbor))
def shoelace_area(corners): def shoelace_area(corners):
n = len(corners) n = len(corners)
area = 0 area = 0
@ -236,7 +254,25 @@ def shoelace_area(corners):
area += (x1 * y2) - (x2 * y1) area += (x1 * y2) - (x2 * y1)
return abs(area) / 2.0 return abs(area) / 2.0
def extract_year_and_date(scriptname) -> tuple[str, str]: def extract_year_and_date(scriptname) -> tuple[str, str]:
r = re.compile(r"aoc(\d\d\d\d)/d(\d+).py") r = re.compile(r"aoc(\d\d\d\d)/d(\d+).py")
[(year, day)] = r.findall(scriptname) [(year, day)] = r.findall(scriptname)
return (year, day) return (year, day)
def get_data(filename):
path, file = os.path.split(filename)
year = path[-4:]
day = file.replace("d", "").replace(".py", "")
txt_file = f"d{day}.txt"
if os.path.isfile(txt_file):
with open(txt_file) as f:
return f.read()
else:
import subprocess
subprocess.call(["../get.py", year, day])
assert os.path.isfile(txt_file), "Could not download AoC file"
with open(txt_file) as f:
return f.read()