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