feat: aggiunge il vincolo DIFFXY e il feedback live durante l'elaborazione
This commit is contained in:
@@ -35,6 +35,7 @@ WORDS = [
|
||||
|
||||
HORIZONTAL = "H"
|
||||
VERTICAL = "V"
|
||||
DIFFXY = 7
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -78,9 +79,28 @@ class CrosswordState:
|
||||
x_min, y_min, x_max, y_max = self.bounds()
|
||||
return (x_max - x_min + 1) * (y_max - y_min + 1)
|
||||
|
||||
def score(self) -> Tuple[int, int, int]:
|
||||
# Maximize placed words first, then intersections, then prefer compact grids.
|
||||
return (self.placed_words, self.intersections, -self.area())
|
||||
def width(self) -> int:
|
||||
x_min, _, x_max, _ = self.bounds()
|
||||
return x_max - x_min + 1
|
||||
|
||||
def height(self) -> int:
|
||||
_, y_min, _, y_max = self.bounds()
|
||||
return y_max - y_min + 1
|
||||
|
||||
def shape_difference(self) -> int:
|
||||
return abs(self.width() - self.height())
|
||||
|
||||
def score(self, diffxy: int) -> Tuple[int, int, int, int, int]:
|
||||
# Prefer valid shapes first; within those, maximize placed words and intersections,
|
||||
# then prefer squarer and more compact grids.
|
||||
is_shape_valid = 1 if self.shape_difference() <= diffxy else 0
|
||||
return (
|
||||
is_shape_valid,
|
||||
self.placed_words,
|
||||
self.intersections,
|
||||
-self.shape_difference(),
|
||||
-self.area(),
|
||||
)
|
||||
|
||||
|
||||
class CrosswordGenerator:
|
||||
@@ -90,6 +110,7 @@ class CrosswordGenerator:
|
||||
*,
|
||||
max_candidates_per_word: int = 12,
|
||||
time_limit_seconds: float = 8.0,
|
||||
diffxy: int = DIFFXY,
|
||||
) -> None:
|
||||
normalized = [self._normalize(word) for word in words]
|
||||
unique_words = list(dict.fromkeys(word for word in normalized if len(word) >= 2))
|
||||
@@ -97,6 +118,7 @@ class CrosswordGenerator:
|
||||
self.best_state = CrosswordState(grid={}, placements=[], intersections=0)
|
||||
self.max_candidates_per_word = max_candidates_per_word
|
||||
self.time_limit_seconds = time_limit_seconds
|
||||
self.diffxy = diffxy
|
||||
self.started_at = 0.0
|
||||
self.visited: Dict[Tuple[frozenset, Tuple[str, ...]], Tuple[int, int, int]] = {}
|
||||
self.nodes_visited = 0
|
||||
@@ -151,7 +173,7 @@ class CrosswordGenerator:
|
||||
if time.perf_counter() - self.started_at > self.time_limit_seconds:
|
||||
return
|
||||
|
||||
if state.score() > self.best_state.score():
|
||||
if state.score(self.diffxy) > self.best_state.score(self.diffxy):
|
||||
self.best_state = state.copy()
|
||||
|
||||
if not remaining_words:
|
||||
@@ -167,9 +189,10 @@ class CrosswordGenerator:
|
||||
|
||||
signature = (frozenset(state.grid.items()), tuple(sorted(remaining_words)))
|
||||
best_seen = self.visited.get(signature)
|
||||
if best_seen is not None and best_seen >= state.score():
|
||||
current_score = state.score(self.diffxy)
|
||||
if best_seen is not None and best_seen >= current_score:
|
||||
return
|
||||
self.visited[signature] = state.score()
|
||||
self.visited[signature] = current_score
|
||||
|
||||
next_word = self._select_next_word(state, remaining_words)
|
||||
candidates = self._generate_candidates(state, next_word)
|
||||
@@ -206,8 +229,8 @@ class CrosswordGenerator:
|
||||
message = (
|
||||
f"\r{frame} elaborazione... "
|
||||
f"nodi={self.nodes_visited} "
|
||||
f"migliore={self.best_state.placed_words} parole, {self.best_state.intersections} incroci "
|
||||
f"attuale={state.placed_words} parole "
|
||||
f"migliore={self.best_state.placed_words} parole, {self.best_state.intersections} incroci, diff={self.best_state.shape_difference()} "
|
||||
f"attuale={state.placed_words} parole, diff={state.shape_difference()} "
|
||||
f"t={elapsed:0.1f}s"
|
||||
)
|
||||
print(message, end="", file=sys.stderr, flush=True)
|
||||
@@ -371,6 +394,8 @@ def main() -> None:
|
||||
print("Miglior soluzione trovata")
|
||||
print(f"Parole inserite: {solution.placed_words}/{len(generator.words)}")
|
||||
print(f"Intersezioni totali: {solution.intersections}")
|
||||
print(f"Dimensioni: {solution.width()} colonne x {solution.height()} righe")
|
||||
print(f"Differenza righe/colonne: {solution.shape_difference()} (vincolo DIFFXY={generator.diffxy})")
|
||||
print(f"Area occupata: {solution.area()}")
|
||||
print()
|
||||
print(render_grid(solution.grid, solution.placements))
|
||||
|
||||
Reference in New Issue
Block a user