Skip to content

Commit 3796f76

Browse files
committed
Add a year path to the import path
1 parent fc1befb commit 3796f76

3 files changed

Lines changed: 105 additions & 107 deletions

File tree

everybody_codes/event2024/quest_20.py

Lines changed: 85 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,69 @@ def solve(part: int, data: str) -> int:
2626
if k.isalpha():
2727
maps["."] |= v
2828

29+
if part == 1:
30+
return p1(maps)
31+
if part == 2:
32+
return p2(maps)
33+
34+
35+
def p2(maps):
36+
points = ["S"] + sorted(i for i in maps if i.isalpha() and i != "S") + ["S"]
37+
deltas = {p: -1 for p in maps["."]} | {p: -2 for p in maps["-"]} | {p: 1 for p in maps["+"]}
38+
39+
def is_cluster(start):
40+
x, y = start
41+
42+
clumps = [
43+
[(x, y), (x + 1, y), (x, y + 1), (x + 1, y + 1)],
44+
[(x, y), (x - 1, y), (x, y + 1), (x - 1, y + 1)],
45+
[(x, y), (x + 1, y), (x, y - 1), (x + 1, y - 1)],
46+
[(x, y), (x - 1, y), (x, y - 1), (x - 1, y - 1)],
47+
]
48+
return any(all(i in maps["+"] for i in clump) for clump in clumps)
49+
50+
logging.debug("Build clusters")
51+
clusters = {i for i in maps["+"] if is_cluster(i)}
52+
logging.debug("Done building clusters. Found %d positions.", len(clusters))
53+
54+
legs = {}
55+
56+
for start, end in zip(points, points[1:]):
57+
logging.debug(f"Compute routes {start}-{end}")
58+
dest = maps[end].copy().pop()
59+
pos = maps[start].copy().pop()
60+
best = {pos: (0, False, frozenset())}
61+
todo = {(pos, False)}
62+
for steps in range(1, 500):
63+
next_todo = set()
64+
for position, hit_cluster in todo:
65+
seen = best[position][2]
66+
next_seen = frozenset(seen | {position})
67+
for d in DIRECTIONS:
68+
next_pos = (position[0] + d[0], position[1] + d[1])
69+
if next_pos not in deltas or next_pos in seen:
70+
continue
71+
next_altitude = best[position][0] + deltas[next_pos]
72+
next_hit = hit_cluster or next_pos in clusters
73+
if next_pos not in best or (position not in best[next_pos][2] and (next_altitude > best[next_pos][0] or (next_altitude == best[next_pos][0] and next_hit))):
74+
best[next_pos] = (next_altitude, next_hit, next_seen)
75+
next_todo.add((next_pos, next_hit))
76+
todo = next_todo
77+
if not todo:
78+
break
79+
legs[end] = (steps,) + best[dest][:2]
80+
81+
log(f"{legs=}")
82+
steps = sum(i[0] for i in legs.values())
83+
delta = -min(sum(i[1] for i in legs.values()), 0)
84+
log(f"{steps=}, {delta=}")
85+
# assert any(i[2] for i in legs.values()) or delta == 0
86+
if delta % 2:
87+
delta += 1
88+
return steps + delta
89+
90+
91+
def p1(maps):
2992
def is_cluster(start):
3093
todo = {
3194
((start[0] + x, start[1] + y), (x, y))
@@ -45,112 +108,33 @@ def is_cluster(start):
45108
todo.add((next_pos, (x, y)))
46109
return False
47110

48-
log("Build clusters")
111+
logging.debug("Build clusters")
49112
clusters = {i for i in maps["+"] if is_cluster(i)}
50-
log("Done building clusters. Found %d positions.", len(clusters))
113+
logging.debug("Done building clusters. Found %d positions.", len(clusters))
51114

52-
53-
if part == 1:
54-
return p1(maps, clusters)
55-
if part == 2:
56-
return p2(maps, clusters)
57-
58-
59-
def manhattan(a, b):
60-
return abs(a[0] - b[0]) + abs(a[1] - b[1])
61-
62-
63-
def p2(maps, clusters):
64-
points = ["S"] + sorted(i for i in maps if i.isalpha() and i != "S") + ["S"]
65-
deltas = {p: -1 for p in maps["."]} | {p: -2 for p in maps["-"]} | {p: 1 for p in maps["+"]}
66-
67-
legs = {}
68-
improvements = collections.defaultdict(dict)
69-
70-
for start, end in zip(points, points[1:]):
71-
log(f"Compute routes {start}-{end}")
72-
dest = maps[end].copy().pop()
73-
pos = maps[start].copy().pop()
74-
q = queue.PriorityQueue()
75-
seen = {(pos, direction) for direction in DIRECTIONS}
76-
min_step_count = None
77-
max_altitude = None
78-
for direction in DIRECTIONS:
79-
# score (manhattan + steps), steps, altitude, pos, direction
80-
q.put((manhattan(pos, dest), 0, 0, pos, direction))
81-
while not q.empty():
82-
_, altitude, steps, position, direction = q.get()
83-
if position == dest:
84-
if min_step_count is None:
85-
min_step_count = steps
86-
max_altitude = altitude
87-
else:
88-
if steps == min_step_count:
89-
max_altitude = max(max_altitude, altitude)
90-
else:
91-
improvements[end][steps - min_step_count] = max(improvements.get(steps - min_step_count, 0), altitude - max_altitude)
92-
continue
93-
94-
if q.qsize() > 10_000_000:
95-
break
96-
97-
steps += 1
98-
if min_step_count and steps > min_step_count + 10:
99-
break
100-
for d in DIRECTIONS - {(-direction[0], -direction[1])}:
101-
next_pos = (position[0] + d[0], position[1] + d[1])
102-
if (next_pos, d) in seen or next_pos not in deltas:
103-
continue
104-
next_altitude = altitude + deltas[next_pos]
105-
q.put((manhattan(next_pos, dest) + steps, next_altitude, steps, next_pos, d))
106-
legs[end] = (min_step_count, max_altitude)
107-
improvements[end][0] = 0
108-
109-
steps = sum(i[0] for i in legs.values())
110-
delta = sum(i[1] for i in legs.values())
111-
log(f"{steps=}, {delta=}, {improvements=}")
112-
113-
if delta >= 0:
114-
return steps
115-
delta = -delta
116-
can_do = set()
117-
for modifications in itertools.product(*[((k, v) for k, v in a.items() if k == 0 or v != 0) for a in improvements.values()]):
118-
extra_steps = sum(i[0] for i in modifications)
119-
extra_elevation = sum(i[1] for i in modifications)
120-
if extra_elevation >= delta:
121-
can_do.add(extra_steps)
122-
return min(can_do) + steps
123-
124-
125-
def p1(maps, clusters):
126115
deltas = {p: -1 for p in maps["."]} | {p: -2 for p in maps["-"]} | {p: 1 for p in maps["+"]}
127-
128116
altitude = 1000
129-
direction = (0, 1) # arbitrary but ought to work in p1
130117
position = maps["S"].pop()
131118

132-
# score = -1 * (steps_remaining + altitude)
133-
q = queue.PriorityQueue()
134-
q.put((-(altitude + 100), altitude, position, direction, 100))
135-
seen = {position}
136-
137-
while not q.empty():
138-
score, altitude, position, direction, steps_left = q.get()
139-
if position in clusters:
140-
return altitude + steps_left
141-
if steps_left == 0:
142-
return altitude
143-
next_steps_left = steps_left - 1
144-
for d in DIRECTIONS - {(-direction[0], -direction[1])}:
145-
next_pos = (position[0] + d[0], position[1] + d[1])
146-
if next_pos in seen or next_pos not in deltas:
147-
continue
148-
if clusters:
149-
seen.add(next_pos)
150-
next_altitude = altitude + deltas[next_pos]
151-
if next_altitude < 0:
152-
continue
153-
q.put((-(next_altitude + next_steps_left), next_altitude, next_pos, d, next_steps_left))
119+
best = {position: 0}
120+
seen = set()
121+
todo = {position}
122+
for steps in range(100):
123+
next_todo = set()
124+
for position in todo:
125+
for d in DIRECTIONS:
126+
next_pos = (position[0] + d[0], position[1] + d[1])
127+
if next_pos in seen or next_pos not in deltas:
128+
continue
129+
next_altitude = best[position] + deltas[next_pos]
130+
next_todo.add(next_pos)
131+
if next_pos not in best or next_altitude > best[next_pos]:
132+
best[next_pos] = next_altitude
133+
seen |= next_todo
134+
todo = next_todo
135+
found = todo & clusters
136+
if found:
137+
return max(best[i] for i in found) + 100 - steps + 1000
154138

155139

156140
TEST_DATA = [
@@ -212,7 +196,7 @@ def p1(maps, clusters):
212196
]
213197
TESTS = [
214198
# (1, TEST_DATA[0], 1045),
215-
# (2, TEST_DATA[1], 24),
199+
(2, TEST_DATA[1], 24),
216200
(2, TEST_DATA[2], 78),
217201
(2, TEST_DATA[3], 206),
218202
# (3, TEST_DATA[2], None),

everybody_codes/runner.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,27 @@ def input_path(self, year: int, day: int, part: int) -> pathlib.Path:
1616
"""Return the input file."""
1717
return pathlib.Path(f"inputs/{day:02}.{part}.txt")
1818

19-
def module_name(self, year: int, day: int) -> str:
19+
def module_path(self) -> str:
20+
return self.year
21+
22+
def module_name(self) -> str:
2023
"""Return the module name."""
21-
return f"quest_{day:02}"
24+
return f"quest_{self.day:02}"
2225

2326

2427
@click.command()
2528
@click.option("--day", "-d", type=int, required=True)
29+
@click.option("--event", "-e", type=str, default="story01")
2630
@click.option("--check", "-c", is_flag=True)
2731
@click.option("--solve", "-s", is_flag=True)
2832
@click.option("--test", "-t", is_flag=True)
2933
@click.option("--part", "-p", "parts", type=int, multiple=True, default=(1, 2, 3))
3034
@click.option("--live", "-l", is_flag=True)
3135
@click.option("--verbose", "-v", count=True)
32-
def main(day: int, check: bool, solve: bool, test: bool, live: bool, parts: tuple[int], verbose: int) -> None:
33-
Runner().run(day, check, solve, test, live, parts, verbose)
36+
def main(day: int, event: str, check: bool, solve: bool, test: bool, live: bool, parts: tuple[int], verbose: int) -> None:
37+
Runner(
38+
year=event, day=day, data=None, parts=parts, verbose=verbose,
39+
).run(check, solve, test, live)
3440

3541

3642
if __name__ == "__main__":

pylib/running.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pathlib
1111
import requests
1212
import resource
13+
import sys
1314
import time
1415
import typing
1516

@@ -28,6 +29,13 @@ class Runner:
2829
parts: collections.abc.Iterable[int]
2930
verbose: bool
3031

32+
def __post_init__(self):
33+
"""Initialize."""
34+
cwd = pathlib.Path(os.getcwd())
35+
module_path = cwd / self.module_path()
36+
if cwd.name != self.module_path() and module_path.exists() and str(module_path) not in sys.path:
37+
sys.path.append(str(module_path))
38+
3139
def solutions_path(self) -> pathlib.Path:
3240
"""Return the solution file."""
3341
raise NotImplemented
@@ -78,7 +86,7 @@ def input_data(self, part: int) -> str:
7886
return data_path.read_text().rstrip()
7987

8088
def run_day(self, check: bool, solve: bool, test: bool, formatter) -> None:
81-
module = importlib.import_module(self.module_name(self.year, self.day))
89+
module = importlib.import_module(self.module_name())
8290
module = importlib.reload(module)
8391
if test:
8492
for part in self.parts:
@@ -128,7 +136,7 @@ def run(self, check: bool, solve: bool, test: bool, live: bool) -> None:
128136
inotify.add_watch(pathlib.Path(__file__).parent, inotify_simple.flags.CLOSE_WRITE)
129137
count = 0
130138
while events := inotify.read():
131-
if not any(i.name == self.module_name(2025, day) + ".py" for i in events):
139+
if not any(i.name == self.module_name() + ".py" for i in events):
132140
continue
133141
count += 1
134142
print(datetime.datetime.now().strftime(f"== {count:02}: %H:%M:%S =="))

0 commit comments

Comments
 (0)