Extend lib and solve day 17 with A*. Fun.
This commit is contained in:
@@ -12,4 +12,8 @@
|
|||||||
- Day 10: 180:00; this one was hard for me.
|
- Day 10: 180:00; this one was hard for me.
|
||||||
- Day 11: 68:00; okay but not elegant and way too slow ofc; x-ray solution would have been neat
|
- Day 11: 68:00; okay but not elegant and way too slow ofc; x-ray solution would have been neat
|
||||||
- Day 12: 52:00 and 22:00 for leaderboard; had the right idea and I am good at this type of problem
|
- Day 12: 52:00 and 22:00 for leaderboard; had the right idea and I am good at this type of problem
|
||||||
|
- ...
|
||||||
- Day 16: 00:27:30 745; best placement so far, of course still horribly slow
|
- Day 16: 00:27:30 745; best placement so far, of course still horribly slow
|
||||||
|
- Day 17: a couple of hours... realized that I need A* after a while; reused
|
||||||
|
implementation from Project Euler but improved with heapq which was super fun
|
||||||
|
|
||||||
|
|||||||
78
d17.py
Normal file
78
d17.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from lib import *
|
||||||
|
|
||||||
|
EXAMPLE = """
|
||||||
|
2413432311323
|
||||||
|
3215453535623
|
||||||
|
3255245654254
|
||||||
|
3446585845452
|
||||||
|
4546657867536
|
||||||
|
1438598798454
|
||||||
|
4457876987766
|
||||||
|
3637877979653
|
||||||
|
4654967986887
|
||||||
|
4564679986453
|
||||||
|
1224686865563
|
||||||
|
2546548887735
|
||||||
|
4322674655533
|
||||||
|
"""
|
||||||
|
|
||||||
|
def solve(i: Input, second=False):
|
||||||
|
g = i.grid2()
|
||||||
|
starts = [((0, 0), (0, None))]
|
||||||
|
|
||||||
|
def is_goal(node):
|
||||||
|
pos, _ = node
|
||||||
|
return pos == (g.n_cols - 1, g.n_rows - 1)
|
||||||
|
|
||||||
|
def neighbors(node):
|
||||||
|
pos, dirs = node
|
||||||
|
repeats, prev_dir = dirs
|
||||||
|
nbs = []
|
||||||
|
for dir in [NORTH, WEST, SOUTH, EAST]:
|
||||||
|
if second:
|
||||||
|
if repeats < 4 and prev_dir is not None and prev_dir != dir:
|
||||||
|
continue
|
||||||
|
if repeats == 10 and prev_dir == dir:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if repeats == 3 and prev_dir == dir:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if prev_dir == NORTH and dir == SOUTH:
|
||||||
|
continue
|
||||||
|
elif prev_dir == SOUTH and dir == NORTH:
|
||||||
|
continue
|
||||||
|
elif prev_dir == EAST and dir == WEST:
|
||||||
|
continue
|
||||||
|
elif prev_dir == WEST and dir == EAST:
|
||||||
|
continue
|
||||||
|
|
||||||
|
nb = add2(pos, dir)
|
||||||
|
if not g.contains(nb):
|
||||||
|
continue
|
||||||
|
nbs.append((nb, (repeats + 1 if dir == prev_dir else 1, dir)))
|
||||||
|
return nbs
|
||||||
|
|
||||||
|
def h(node):
|
||||||
|
pos, _ = node
|
||||||
|
return abs(g.n_rows - 1 - pos[0]) + abs(g.n_cols - 1 - pos[1])
|
||||||
|
|
||||||
|
def d(_, b):
|
||||||
|
pos, _ = b
|
||||||
|
if pos == (0, 0):
|
||||||
|
return 0
|
||||||
|
return int(g[pos])
|
||||||
|
|
||||||
|
a = A_Star(starts, is_goal, h, d, neighbors)
|
||||||
|
return a.cost
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DAY_INPUT = "i17.txt"
|
||||||
|
print("Example 1:", solve(Input(EXAMPLE)))
|
||||||
|
print("Solution 1:", solve(Input(DAY_INPUT)))
|
||||||
|
print("Example 2:", solve(Input(EXAMPLE), True))
|
||||||
|
print("Solution 2:", solve(Input(DAY_INPUT), True))
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
39
dx.py
39
dx.py
@@ -1,43 +1,30 @@
|
|||||||
import lib
|
from lib import *
|
||||||
|
|
||||||
EXAMPLE = """
|
EXAMPLE = """
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def solve(lines: list[str]):
|
def solve(i: Input, second=False):
|
||||||
res = 0
|
res = 0
|
||||||
|
i.stats()
|
||||||
# g = list(map(list, lines))
|
# g = i.grid2()
|
||||||
# for (ri, r) in enumerate(g):
|
# ls = i.lines()
|
||||||
# for (ci, c) in enumerate(r):
|
# ps = i.paras()
|
||||||
# pass
|
|
||||||
|
|
||||||
for (i, line) in enumerate(lines):
|
|
||||||
print(i, line)
|
|
||||||
# digits = lib.str_to_int_list(line)
|
|
||||||
# digit = lib.str_to_single_int(line)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def solve2(lines: list[str]):
|
|
||||||
res = 0
|
|
||||||
for (i, line) in enumerate(lines):
|
|
||||||
print(i, line)
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
lines = lib.str_to_lines_no_empty(EXAMPLE)
|
DAY_INPUT = "ix.txt"
|
||||||
print("Example 1:", solve(lines))
|
|
||||||
|
print("Example 1:", solve(Input(EXAMPLE)))
|
||||||
return
|
return
|
||||||
|
|
||||||
lines = lib.str_to_lines_no_empty(open("ix.txt").read())
|
print("Solution 1:", solve(Input(DAY_INPUT)))
|
||||||
print("Solution 1:", solve(lines))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
lines = lib.str_to_lines_no_empty(EXAMPLE)
|
print("Example 2:", solve(Input(EXAMPLE), True))
|
||||||
print("Example 2:", solve2(lines))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
lines = lib.str_to_lines_no_empty(open("ix.txt").read())
|
print("Solution 2:", solve(Input(DAY_INPUT), True))
|
||||||
print("Solution 2:", solve2(lines))
|
return
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
178
lib.py
178
lib.py
@@ -1,4 +1,134 @@
|
|||||||
import re
|
import re
|
||||||
|
import os
|
||||||
|
import string
|
||||||
|
import heapq
|
||||||
|
|
||||||
|
NUMBERS = string.digits
|
||||||
|
LETTERS_LOWER = string.ascii_lowercase
|
||||||
|
LETTERS_UPPER = string.ascii_uppercase
|
||||||
|
|
||||||
|
UP = (-1, 0)
|
||||||
|
DOWN = (1, 0)
|
||||||
|
RIGHT = (0, 1)
|
||||||
|
LEFT = (0, -1)
|
||||||
|
|
||||||
|
NORTH = UP
|
||||||
|
SOUTH = DOWN
|
||||||
|
EAST = RIGHT
|
||||||
|
WEST = LEFT
|
||||||
|
|
||||||
|
INF = float("inf")
|
||||||
|
fst = lambda l: l[0]
|
||||||
|
snd = lambda l: l[1]
|
||||||
|
|
||||||
|
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:
|
||||||
|
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]]:
|
||||||
|
r = []
|
||||||
|
for row_i in range(self.n_rows):
|
||||||
|
for col_i in range(self.n_cols):
|
||||||
|
c = (row_i, col_i)
|
||||||
|
if self[c] in chars:
|
||||||
|
r.append(c)
|
||||||
|
return r
|
||||||
|
|
||||||
|
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 neighbors_ort(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
|
||||||
|
ort_rel = [(-1, 0), (0, 1), (1, 0), (0, -1)]
|
||||||
|
return [add2(pos, off) for off in ort_rel if self.contains(add2(pos, off))]
|
||||||
|
|
||||||
|
def neighbors_vert(self, pos: tuple[int, int]) -> list[tuple[int, int]]:
|
||||||
|
ort_vert = [(-1, -1), (-1, 1), (1, 1), (1, -1)]
|
||||||
|
return [add2(pos, off) for off in ort_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 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[list[str]]:
|
||||||
|
return [p.splitlines() for p in self.text.split("\n\n")]
|
||||||
|
|
||||||
|
def grid2(self) -> Grid2D:
|
||||||
|
return Grid2D(self.text)
|
||||||
|
|
||||||
def prime_factors(n):
|
def prime_factors(n):
|
||||||
"""
|
"""
|
||||||
@@ -32,14 +162,15 @@ def lcm(numbers: list[int]) -> int:
|
|||||||
s *= f
|
s *= f
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def str_to_single_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+")
|
||||||
for m in r.findall(line):
|
m = r.findall(line)
|
||||||
return int(m)
|
assert len(m) == 0, "str_to_int no int"
|
||||||
raise Exception("No single digit sequence in '{line}'")
|
assert len(m) > 1, "str_to_int multiple ints"
|
||||||
|
return int(m[0])
|
||||||
|
|
||||||
def str_to_int_list(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)))
|
||||||
|
|
||||||
@@ -48,3 +179,40 @@ def str_to_lines_no_empty(text: str) -> list[str]:
|
|||||||
|
|
||||||
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):
|
||||||
|
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))
|
||||||
|
|||||||
39
monitor.py
Executable file
39
monitor.py
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_hash(filename):
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
hasher.update(f.read())
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def main(script_name, interval=1):
|
||||||
|
last_hash = None
|
||||||
|
process = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
current_hash = get_file_hash(script_name)
|
||||||
|
if current_hash != last_hash:
|
||||||
|
last_hash = current_hash
|
||||||
|
if process and process.poll() is None:
|
||||||
|
process.terminate()
|
||||||
|
print(f"Detected change in {script_name}, running script...")
|
||||||
|
process = subprocess.Popen(['pypy3', script_name], shell=False)
|
||||||
|
time.sleep(interval)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
if process:
|
||||||
|
process.terminate()
|
||||||
|
break
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("The file was not found. Make sure the script name is correct.")
|
||||||
|
break
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1])
|
||||||
Reference in New Issue
Block a user