J'ai un mot croisé et une liste de mots qui peuvent être utilisés pour le résoudre (les mots peuvent être placés plusieurs fois ou même pas une fois ). Il y a toujours une solution pour les mots croisés et la liste de mots donnés.
J'ai cherché des indices sur la façon de résoudre ce problème et j'ai découvert qu'il s'agissait de NP-Complete. Ma taille maximale de mots croisés est de 250 sur 250, la longueur maximale de la liste (nombre de mots pouvant être utilisés pour le résoudre) est de 200. Mon objectif est de résoudre les mots croisés de cette taille par la force brute/le retour en arrière, ce qui devrait être possible quelques secondes (c'est une estimation approximative de ma part, corrigez-moi si je me trompe).
Exemple:
Une liste de mots donnés pouvant être utilisés pour résoudre les mots croisés:
Les mots croisés vides donnés (X sont des champs qui ne peuvent pas être remplis, les champs vides doivent être remplis):
La solution:
Maintenant, mon approche actuelle consiste à représenter les mots croisés sous forme de tableau à deux dimensions et à rechercher des espaces vides (deux itérations sur les mots croisés). Ensuite, je fais correspondre les mots aux espaces vides en fonction de leur longueur, puis j'essaie toutes les combinaisons de mots pour créer des espaces vides de même longueur. Cette approche est devenue très compliquée très rapidement, je me suis perdue en essayant de la mettre en œuvre, existe-t-il une solution plus élégante?
L'idée de base que vous avez est assez sensée:
C'est un excellent plan. L'étape suivante consiste à le transformer en un dessin. Pour de petits programmes comme celui-ci, nous pouvons passer directement au pseudo-code. En résumé: expliqué par d’autres réponses, est récursion :
1 Draw a slot from the slot pool.
2 If slot pool is empty (all slots filled), stop solving.
3 For each Word with correct length:
4 If part of the slot is filled, check conflict.
5 If the Word does not fit, continue the loop to next Word.
// No conflict
6 Fill the slot with the Word.
// Try next slot (down a level)
7 Recur from step 1.
8 If the recur found no solution, revert (take the Word back) and try next.
// None of them works
9 If no words yield a solution, an upper level need to try another Word.
Revert (put the slot back) and go back.
Vous trouverez ci-dessous un exemple court mais complet que je vous ai concocté à partir de vos exigences.
Il existe plusieurs façons de traiter un chat. Mon code a permuté les étapes 1 et 2 et combine les étapes 4 à 6 dans une boucle de remplissage.
Points clés:
clone()
et restaurée par arraycopy .La source:
import Java.awt.Point;
import Java.util.*;
import Java.util.function.BiFunction;
import Java.util.function.Supplier;
import Java.util.stream.Stream;
public class Crossword {
public static void main ( String[] args ) {
new Crossword( Arrays.asList( "5 4 4\n#_#_#\n_____\n#_##_\n#_##_\ntuna\nmusic\ncan\nhi".split( "\n" ) ) );
new Crossword( Arrays.asList( "6 6 4\n##_###\n#____#\n___#__\n#_##_#\n#____#\n##_###\nnice\npain\npal\nid".split( "\n" ) ) );
}
private final int height, width; // Board size
private final char[] board; // Current board state. _ is unfilled. # is blocked. other characters are filled.
private final Set<String> words; // List of words
private final Map<Point, Integer> vertical = new HashMap<>(), horizontal = new HashMap<>(); // Vertical and horizontal slots
private String indent = ""; // For formatting log
private void log ( String message, Object... args ) { System.out.println( indent + String.format( message, args ) ); }
private Crossword ( List<String> lines ) {
// Parse input data
final int[] sizes = Stream.of( lines.get(0).split( "\\s+" ) ).mapToInt( Integer::parseInt ).toArray();
width = sizes[0]; height = sizes[1];
board = String.join( "", lines.subList( 1, height+1 ) ).toCharArray();
words = new HashSet<>( lines.subList( height+1, lines.size() ) );
// Find horizontal slots then vertical slots
for ( int y = 0, size ; y < height ; y++ )
for ( int x = 0 ; x < width-1 ; x++ )
if ( isSpace( x, y ) && isSpace( x+1, y ) ) {
for ( size = 2 ; x+size < width && isSpace( x+size, y ) ; size++ ); // Find slot size
horizontal.put( new Point( x, y ), size );
x += size; // Skip past this horizontal slot
}
for ( int x = 0, size ; x < width ; x++ )
for ( int y = 0 ; y < height-1 ; y++ )
if ( isSpace( x, y ) && isSpace( x, y+1 ) ) {
for ( size = 2 ; y+size < height && isSpace( x, y+size ) ; size++ ); // Find slot size
vertical.put( new Point( x, y ), size );
y += size; // Skip past this vertical slot
}
log( "A " + width + "x" + height + " board, " + vertical.size() + " vertical, " + horizontal.size() + " horizontal." );
// Solve the crossword, horizontal first then vertical
final boolean solved = solveHorizontal();
// Show board, either fully filled or totally empty.
for ( int i = 0 ; i < board.length ; i++ ) {
if ( i % width == 0 ) System.out.println();
System.out.print( board[i] );
}
System.out.println( solved ? "\n" : "\nNo solution found\n" );
}
// Helper functions to check or set board cell
private char get ( int x, int y ) { return board[ y * width + x ]; }
private void set ( int x, int y, char character ) { board[ y * width + x ] = character; }
private boolean isSpace ( int x, int y ) { return get( x, y ) == '_'; }
// Fit all horizontal slots, when success move to solve vertical.
private boolean solveHorizontal () {
return solve( horizontal, this::fitHorizontal, "horizontally", this::solveVertical );
}
// Fit all vertical slots, report success when done
private boolean solveVertical () {
return solve( vertical, this::fitVertical, "vertically", () -> true );
}
// Recur each slot, try every Word in a loop. When all slots of this kind are filled successfully, run next stage.
private boolean solve ( Map<Point, Integer> slot, BiFunction<Point, String, Boolean> fill, String dir, Supplier<Boolean> next ) {
if ( slot.isEmpty() ) return next.get(); // If finished, move to next stage.
final Point pos = slot.keySet().iterator().next();
final int size = slot.remove( pos );
final char[] state = board.clone();
/* Try each Word */ indent += " ";
for ( String Word : words ) {
if ( Word.length() != size ) continue;
/* If the Word fit, recur. If recur success, done! */ log( "Trying %s %s at %d,%d", Word, dir, pos.x, pos.y );
if ( fill.apply( pos, Word ) && solve( slot, fill, dir, next ) )
return true;
/* Doesn't match. Restore board and try next Word */ log( "%s failed %s at %d,%d", Word, dir, pos.x, pos.y );
System.arraycopy( state, 0, board, 0, board.length );
}
/* No match. Restore slot and report failure */ indent = indent.substring( 0, indent.length() - 2 );
slot.put( pos, size );
return false;
}
// Try fit a Word to a slot. Return false if there is a conflict.
private boolean fitHorizontal ( Point pos, String Word ) {
final int x = pos.x, y = pos.y;
for ( int i = 0 ; i < Word.length() ; i++ ) {
if ( ! isSpace( x+i, y ) && get( x+i, y ) != Word.charAt( i ) ) return false; // Conflict
set( x+i, y, Word.charAt( i ) );
}
return true;
}
private boolean fitVertical ( Point pos, String Word ) {
final int x = pos.x, y = pos.y;
for ( int i = 0 ; i < Word.length() ; i++ ) {
if ( ! isSpace( x, y+i ) && get( x, y+i ) != Word.charAt( i ) ) return false; // Conflict
set( x, y+i, Word.charAt( i ) );
}
return true;
}
}
Exercice: Vous pouvez réécrire une récursivité à une itération; plus rapide et peut supporter de plus grandes planches. Une fois cela fait, il peut être converti en multi-thread et courir encore plus vite.
Pour rendre ce problème plus facile à résoudre, je vais le décomposer en problèmes plus petits et plus faciles. Notez que je n’inclus pas le code/les algorithmes, car je pense que cela n’aidera pas ici (si nous voulions le meilleur code, il y aurait des index, des bases de données et une magie noire qui ferait exploser votre tête en le voyant). Au lieu de cela, cette réponse tente de répondre à la question en parlant de méthodes de pensée qui aideront le PO à s’attaquer à ce problème (et à ceux à venir) en utilisant la méthode qui convient le mieux au lecteur.
Cette réponse suppose que vous savez comment procéder comme suit
Ainsi, il est assez facile de charger vos mots croisés dans une matrice n par m (tableau 2D, ci-après «grille»), mais il est très clair que cela fonctionne avec pragmatisme. Commençons donc par analyser vos mots croisés d’une grille à un objet légitime.
Pour autant que votre programme ait besoin de le savoir, chaque entrée dans les mots croisés a 4 propriétés.
Vous pouvez les trouver dans la grille en fonction de ces règles lors de l'analyse.
Dans votre objet de mots croisés, vous pouvez stocker les entrées en utilisant la coordonnée + direction comme clé pour faciliter la référence et la conversion facile vers/à partir du formulaire de grille de texte.
Vous devriez maintenant avoir un objet contenant une collection d'entrées de mots croisés, contenant leurs liaisons d'index pertinentes. Vous devez maintenant trouver un ensemble de valeurs qui satisferont toutes vos entrées.
Vos objets d'entrée doivent avoir des méthodes d'assistance telles que isValidEntry(str)
qui vérifie la valeur donnée et l'état actuel des mots croisés, puis-je mettre ce mot ici? En rendant chaque objet de votre modèle responsable de son propre niveau de logique, le code correspondant au problème posé par la pensée peut simplement appeler la logique sans se soucier de son implémentation (dans cet exemple, votre solveur n'a pas à s'inquiéter de la logique. of est une valeur valide, il peut simplement demander isValidEntry
pour cela)
Si vous avez fait ce qui est dit ci-dessus, résoudre le problème est alors une simple question d’itération sur tous les mots pour que toutes les entrées trouvent une solution.
Pour référence, voici ma liste de sous problèmes que vous devez écrire quelque chose à résoudre.
Vous avez raison, le problème est NP
- complet. Donc, votre meilleure chance est de le résoudre par la force brute (si vous trouvez un algorithme polynomial, dites-le-moi, nous pouvons tous deux être riches =)).
Ce que je vous suggère est de regarderbacktracking. Cela vous permettra d’écrire une solution élégante (et néanmoins lente compte tenu de la taille de votre saisie) au problème des mots croisés.
Si vous avez besoin de matériel plus stimulant, jetez un coup d'œil à ce solveur qui utilise le retour en arrière comme méthode de navigation dans l'arborescence de la solution.
Notez qu'il existe des algorithmes qui pourraient en pratique fonctionner mieux qu'une force brute pure (même si leur complexité est toujours exponentielle). De plus, une recherche rapide sur scholar révèle un bon nombre d'articles. sur le sujet que vous voudrez peut-être examiner, tels que les suivants:
Un mot croisé est un problème de satisfaction de contrainte qui est généralement un NP-Complet, mais de nombreux solveurs appliqueront les algorithmes les plus efficaces à un problème de contrainte que vous spécifiez. Le solutionneur Z3 SMT peut résoudre ces problèmes très facilement et à grande échelle. Tout ce que vous avez à faire est d’écrire un programme Java qui transforme le jeu de mots croisés en un problème SMT que le solutionneur peut comprendre puis le donne au solutionneur pour le résoudre. Z3 a des liaisons Java donc ça devrait être assez simple. J'ai écrit le code Z3 pour résoudre le premier exemple ci-dessous. Il ne devrait pas être difficile pour vous de suivre le modèle de votre programme Java pour spécifier des énigmes de croisement arbitrairement grandes.
; Declare each possible Word as string literals
(define-const str1 String "tuna")
(define-const str2 String "music")
(define-const str3 String "can")
(define-const str4 String "hi")
; Define a function that returns true if the given String is equal to one of the possible words defined above.
(define-fun validString ((s String)) Bool
(or (= s str1) (or (= s str2) (or (= s str3) (= s str4)))))
; Declare the strings that need to be solved
(declare-const unknownStr1 String)
(declare-const unknownStr2 String)
(declare-const unknownStr3 String)
(declare-const unknownStr4 String)
; Assert the correct lengths for each of the unknown strings.
(assert (= (str.len unknownStr1) 4))
(assert (= (str.len unknownStr2) 5))
(assert (= (str.len unknownStr3) 3))
(assert (= (str.len unknownStr4) 2))
; Assert each of the unknown strings is one of the possible words.
(assert (validString unknownStr1))
(assert (validString unknownStr2))
(assert (validString unknownStr3))
(assert (validString unknownStr4))
; Where one Word in the crossword puzzle intersects another assert that the characters at the intersection point are equal.
(assert (= (str.at unknownStr1 1) (str.at unknownStr2 1)))
(assert (= (str.at unknownStr2 3) (str.at unknownStr4 1)))
(assert (= (str.at unknownStr2 4) (str.at unknownStr3 0)))
; Solve the model
(check-sat)
(get-model)
Je recommande le solveur Z3 SMT, mais il existe de nombreux autres solveurs de contraintes. Vous n'avez pas besoin non plus d'implémenter votre propre algorithme de résolution de contraintes, mais également d'implémenter votre propre algorithme de tri.