small fixes

main
Harald Holtmann 2025-09-07 21:53:39 +02:00
parent e53b2cb918
commit 4cdca1e9a1
5 changed files with 115 additions and 74 deletions

@ -21,7 +21,9 @@ class APIError(Exception):
def select(problem: str): def select(problem: str):
response = requests.post(config.contest_url + "/select", json={"problemName": problem, "id": config.id}) response = requests.post(
config.contest_url + "/select", json={"problemName": problem, "id": config.id}
)
if not response.ok: if not response.ok:
raise APIError(f"{response.status_code}: {response.text}") raise APIError(f"{response.status_code}: {response.text}")
@ -43,7 +45,6 @@ def init_explore_cache():
def write_explore_cache(): def write_explore_cache():
return
with open("explore_cache.json", "w") as h: with open("explore_cache.json", "w") as h:
h.write(json.dumps(explore_cache)) h.write(json.dumps(explore_cache))

@ -1,68 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import itertools import itertools
import functools
import json import json
import os.path import os.path
import sys import sys
import random
from collections import defaultdict from collections import defaultdict
import api import api
import graph import graph
from path import DOORS, Path
class ExploreError(Exception): class ExploreError(Exception):
pass pass
@functools.total_ordering
class Path:
def __init__(self, path=""):
if isinstance(path, list):
path = "".join(str(p) for p in path)
self.path = path
def __repr__(self):
return "." + self.path if self.path else "."
def __bool__(self):
return bool(self.path)
def __eq__(self, other):
return self.path == other.path
def __lt__(self, other):
return (len(self.path), self.path) < (len(other.path), other.path)
def __hash__(self):
return self.path.__hash__()
def __format__(self, spec):
return str(self).__format__(spec)
def __add__(self, other):
return Path(self.path + other.path)
def __len__(self):
return len(self.path)
def __getitem__(self, idx):
return Path(self.path.__getitem__(idx))
def last(self):
return Path(self.path[:-1]), int(self.path[-1])
def shorten(self, path, pmerge):
if self.path.startswith(pmerge.path):
return Path(path.path + self.path[len(pmerge.path) :])
else:
return self
DOORS = [Path(str(i)) for i in range(6)]
class Explore: class Explore:
def __init__(self, problem, probes): def __init__(self, problem, probes):
self.problem = problem self.problem = problem
@ -104,7 +56,7 @@ class Explore:
for path, presults in zip(paths, itertools.batched(results, len(probes))): for path, presults in zip(paths, itertools.batched(results, len(probes))):
prefix_len = len(mark) - 2 * num_marks + len(path) prefix_len = len(mark) - 2 * num_marks + len(path)
print("id", path, results) # print("id", path, results)
self.update(path, [res[prefix_len:] for res in presults]) self.update(path, [res[prefix_len:] for res in presults])
def _add_room(self, path, results): def _add_room(self, path, results):
@ -143,7 +95,9 @@ class Explore:
# self.dump() # self.dump()
p, rid = self.neighbors[pl][d] p, rid = self.neighbors[pl][d]
assert rid is None or rid == room_id, f"penultimate {path} {pl}: {rid} != {room_id}" assert (
rid is None or rid == room_id
), f"penultimate {path} {pl}: {rid} != {room_id}"
self.neighbors[pl][d] = (p, room_id) self.neighbors[pl][d] = (p, room_id)
# self.dump() # self.dump()
@ -177,7 +131,7 @@ class Explore:
try to unify rooms at paths p1, p2 try to unify rooms at paths p1, p2
return unified rooms return unified rooms
""" """
print("unify", path1, path2) # print("unify", path1, path2)
if path1 == path2: if path1 == path2:
return return
@ -185,7 +139,9 @@ class Explore:
assert path2 in self.rooms, f"room '{path2}' not explored" assert path2 in self.rooms, f"room '{path2}' not explored"
if self.rooms[path1] != self.rooms[path2]: if self.rooms[path1] != self.rooms[path2]:
raise ExploreError(f"ids of '{path1}'({self.rooms[path1]}) and '{path2}'({self.rooms[path2]}) do not match") raise ExploreError(
f"ids of '{path1}'({self.rooms[path1]}) and '{path2}'({self.rooms[path2]}) do not match"
)
path = min(path1, path2) path = min(path1, path2)
pmerge = max(path1, path2) pmerge = max(path1, path2)
@ -194,9 +150,13 @@ class Explore:
self.unification_id[pmerge] = path self.unification_id[pmerge] = path
merged_neighbors = [] merged_neighbors = []
for n, ((p, rid), (pm, rmid)) in enumerate(zip(self.neighbors[path], self.neighbors[pmerge])): for n, ((p, rid), (pm, rmid)) in enumerate(
zip(self.neighbors[path], self.neighbors[pmerge])
):
if rid and rmid and rid != rmid: if rid and rmid and rid != rmid:
raise ExploreError(f"neighbor {n} of '{path}'({rid}) and '{pmerge}'({rmid}) do not match") raise ExploreError(
f"neighbor {n} of '{path}'({rid}) and '{pmerge}'({rmid}) do not match"
)
merged_neighbors.append((self._path(p), rid or rmid)) merged_neighbors.append((self._path(p), rid or rmid))
# fix rooms # fix rooms
@ -224,7 +184,9 @@ class Explore:
new.append((np_, rid)) new.append((np_, rid))
if rid: if rid:
try: try:
assert np_ in self.rooms, f"unify: path {np} {np_} of {(p, ns)} not in rooms" assert (
np_ in self.rooms
), f"unify: path {np} {np_} of {(p, ns)} not in rooms"
except AssertionError as exc: except AssertionError as exc:
self.dump() self.dump()
raise exc raise exc
@ -253,7 +215,9 @@ class Explore:
yield unexplored yield unexplored
def is_explored(self): def is_explored(self):
return next(self.unexplored(), None) is None and all(len(p) == 1 for p in self.room_ids.values()) return next(self.unexplored(), None) is None and all(
len(p) == 1 for p in self.room_ids.values()
)
def layout(self, orig=None, start=Path()): def layout(self, orig=None, start=Path()):
aedi = graph.Aedificium(self.problem) aedi = graph.Aedificium(self.problem)
@ -271,12 +235,18 @@ class Explore:
for src_door, (trg_path, _) in enumerate(ns): for src_door, (trg_path, _) in enumerate(ns):
src = (src_path, src_door) src = (src_path, src_door)
trg_door = next( trg_door = next(
(j for j, (p, rid) in enumerate(self.neighbors[trg_path]) if p == src_path), (
j
for j, (p, rid) in enumerate(self.neighbors[trg_path])
if p == src_path
),
None, None,
) )
if trg_door is None: if trg_door is None:
# raise Exception(f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}") # raise Exception(f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}")
print(f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}") print(
f"backlink not found: {(src, trg_path, self.neighbors[trg_path])}"
)
connect_errors.append((src[0], src[1], trg_path)) connect_errors.append((src[0], src[1], trg_path))
trg_door = 0 trg_door = 0
trg = (trg_path, trg_door) trg = (trg_path, trg_door)
@ -295,8 +265,14 @@ class Explore:
if connect_errors: if connect_errors:
# try to fix connection issues # try to fix connection issues
updated = False updated = False
src = {src_path: (trg_path, src_door) for src_path, src_door, trg_path in connect_errors} src = {
trg = {trg_path: (src_path, src_door) for src_path, src_door, trg_path in connect_errors} src_path: (trg_path, src_door)
for src_path, src_door, trg_path in connect_errors
}
trg = {
trg_path: (src_path, src_door)
for src_path, src_door, trg_path in connect_errors
}
for trg_path, (src_path, src_door) in trg.items(): for trg_path, (src_path, src_door) in trg.items():
if trg_path in src: if trg_path in src:
@ -368,7 +344,9 @@ def mark_solve(problem, nrooms):
mark_loop = mark_loop + p + get_mark(rid, mask) + q mark_loop = mark_loop + p + get_mark(rid, mask) + q
print("marked", mark_loop, loop) print("marked", mark_loop, loop)
assert exs[-1].walk(loop) == Path(), f"loop doesn't close {exs[-1].walk(loop)}" assert (
exs[-1].walk(loop) == Path()
), f"loop doesn't close {exs[-1].walk(loop)}"
ex = Explore(problem, probes) ex = Explore(problem, probes)
exs.append(ex) exs.append(ex)
@ -391,7 +369,9 @@ def mark_solve(problem, nrooms):
assert ex.is_explored(), "not fully explored" assert ex.is_explored(), "not fully explored"
if nrooms % len(ex.rooms) != 0: if nrooms % len(ex.rooms) != 0:
raise ExploreError(f"not all rooms could be identifed {len(ex.rooms)}/{nrooms}") raise ExploreError(
f"not all rooms could be identifed {len(ex.rooms)}/{nrooms}"
)
if len(ex.rooms) == nrooms: if len(ex.rooms) == nrooms:
print("found all rooms") print("found all rooms")
@ -445,7 +425,9 @@ if __name__ == "__main__":
with open(os.path.join("..", "problems.json")) as h: with open(os.path.join("..", "problems.json")) as h:
problems = json.loads(h.read()) problems = json.loads(h.read())
problems = {p["problem"]: {"size": p["size"], "idx": i} for i, p in enumerate(problems)} problems = {
p["problem"]: {"size": p["size"], "idx": i} for i, p in enumerate(problems)
}
if problem not in problems: if problem not in problems:
raise ExploreError(f"unknown problem {problem}") raise ExploreError(f"unknown problem {problem}")

@ -6,13 +6,21 @@ if [ -z "$PROBLEMS" ] ; then
PROBLEMS=$(jq -r '.[]|.problem' ../problems.json) PROBLEMS=$(jq -r '.[]|.problem' ../problems.json)
fi fi
if [ $TRIES -eq 0 ]; then
FOREVER=1
TRIES=1
fi
TRIES=${TRIES:-10} TRIES=${TRIES:-10}
for problem in $PROBLEMS; do while : ; do
for t in $(seq $TRIES); do for problem in $PROBLEMS; do
./explore.py $problem for t in $(seq $TRIES); do
if [ $? -eq 0 ]; then ./explore.py $problem
break if [ $? -eq 0 ]; then
fi break
fi
done
done done
[ -n "$FOREVER" ] || break
done done

@ -12,7 +12,9 @@ class Aedificium:
def add_edge(self, scr, src_d, trg, trg_d): def add_edge(self, scr, src_d, trg, trg_d):
self.graph.add_edge( self.graph.add_edge(
pydot.Edge(scr, trg) # , headport=self.PORTS[src_d], tailport=self.PORTS[trg_d]) pydot.Edge(
scr, trg
) # , headport=self.PORTS[src_d], tailport=self.PORTS[trg_d])
) )
def render(self): def render(self):

@ -0,0 +1,48 @@
import functools
@functools.total_ordering
class Path:
def __init__(self, path=""):
if isinstance(path, list):
path = "".join(str(p) for p in path)
self.path = path
def __repr__(self):
return "." + self.path if self.path else "."
def __bool__(self):
return bool(self.path)
def __eq__(self, other):
return self.path == other.path
def __lt__(self, other):
return (len(self.path), self.path) < (len(other.path), other.path)
def __hash__(self):
return self.path.__hash__()
def __format__(self, spec):
return str(self).__format__(spec)
def __add__(self, other):
return Path(self.path + other.path)
def __len__(self):
return len(self.path)
def __getitem__(self, idx):
return Path(self.path.__getitem__(idx))
def last(self):
return Path(self.path[:-1]), int(self.path[-1])
def shorten(self, path, pmerge):
if self.path.startswith(pmerge.path):
return Path(path.path + self.path[len(pmerge.path) :])
else:
return self
DOORS = [Path(str(i)) for i in range(6)]