Als ich das japanische Kreuzworträtsel in 4 Stunden schrieb

Ich sehe mir faul die Liste der Kurse an, die von Schulkindern gehalten wurden, die kürzlich von Kollegen von Sirius angelegt wurden ... Also, was ist das? "Mit SAT-Solvern nach kombinatorischen Objekten suchen"? "Wir haben einen Sudoku-Löser, japanische Kreuzworträtsel und mehr gemacht?"

Der Gedanke taucht auf, dass die erschöpfenden NP-Probleme aufeinander reduziert werden können und insbesondere auf die Suche nach der Machbarkeit einer Booleschen Formel reduziert werden können . Einer der Autoren von Habr hat diese Idee hier zum Ausdruck gebracht , und ehrlich gesagt ist es für mich wie Magie.

Natürlich weiß ich als Person, die einen Kurs in diskreter Mathematik und der Komplexität von Algorithmen abgeschlossen hat, theoretisch, dass Aufgaben aufeinander reduziert werden können. Aber es ist normalerweise schwierig, dies zu tun, und ich nehme besser mein Wort für die Professoren und andere kluge Leute.

Aber dann wird es angeboten ... SCHULKINDER! Im Inneren begann die Ahle Kreativität zu erregen und sagte: "Nun, es ist wahrscheinlich nicht schwierig, sie zu befestigen, sobald die Schüler angeboten werden. Kann ich es nicht herausfinden? Schauen Sie, sie versprechen, dass sie die Python-Bibliothek verwenden werden, aber ich kenne die Python im Allgemeinen ... “

Und es war ungefähr 21 Uhr, was meinen kritischen Blick auf die Komplexität des Problems etwas trübte ... (tatsächlich weitere Chroniken der 4-Stunden-Programmierung)

21:02 Die erste Phase der Quest - Ich versuche, die Bibliothek in Python zu installieren. Aus Erfahrung mit neuronalen Netzen weiß ich bereits, dass Code manchmal weniger Zeit benötigt als das Konfigurieren von Paketen.

Bei Verwendung der klassischen Pip-Installation ist Pycosat nicht installiert, es entsteht ein Fehler im Sinne von Microsoft Visual C ++ 14.0 .

Wir klettern, um nach der Ursache des Problems zu suchen , und gelangen zu Stackoverflow , wo wir einen kleinen Link zu den erforderlichen Visual C ++ 2015 Build Tools finden , anscheinend einschließlich des C ++ - Compilers.

Herunterladen, versuchen zu installieren ... wow, es erfordert 4 GB! Ich brauche eigentlich nur einen Compiler, aber na ja.

Während dieses Ding heruntergeladen und installiert wird, folgen wir den Links im Programm von Sirius ... Ja, nicht viele - Beispiele für Python-Projekte und eine Liste möglicher Aufgaben für den Kurs. Keine Präsentationen, keine Videos, um schnell zu verstehen, wie man mit der Bibliothek arbeitet.

Okay, beginnen wir mit dem Parsen der Beispiele. Wir gehen zu queens_pycosat.py ... Wow, auf dem Weg dorthin ist dies die Lösung für das bekannte Problem, Königinnen auf ein Schachbrett zu legen (die Aufgabe besteht darin, 8 Königinnen so anzuordnen, dass sie sich nicht gegenseitig schlagen).

21: 10-21: 30 Wir verstehen und versuchen zu beginnen.

Die allgemeine Logik beginnt zu verfolgen:
SAT- () (1, 2, 3),(-1, 2 ..), (True/False).

, , .

( , ); , , 8x8 = 64 , .

(1 2 3… 8) — 1 . — [1,2,3...8]
— (9… 16) ..

— 1 !
[ 1 2] ( — ). [-1,-2],[-1,-3] .

, , . , .

( ) — (1 2… 8) ( 1 2) (...). , — .

Ja, ich war nicht sofort so schlau, jetzt wo ich es herausgefunden habe ...

Im Allgemeinen kopieren wir den Lehrcode für uns. Um sicherzustellen, dass wir etwas verstehen, deaktivieren Sie die Prüfung auf Diagonalen - dann wird die Aufgabe zu einer Aufgabe über Türme.

Wir fangen an, wir erhalten: Es funktioniert! 21:35 Nachdem wir uns ein wenig mit der Bibliotheksdokumentation vertraut gemacht haben , stellen wir fest, dass es schwierig ist, eine oder alle Lösungen zu finden. Sag mir, mein lieber Freund, wie viele Turmarrangements gibt es auf einem 8x8-Brett? Wirst du alles finden? Die Maschine dachte eine Weile nach und ich fing an, die Fakultätstabelle zu googeln. Eine Minute später antwortete der Löser: "40320 Lösungen wurden gefunden." Check - wirklich 8!, Alles ist richtig.

| | | | | | | |x|
| | | | | | |x| |
| | | | | |x| | |
| | | | |x| | | |
| | | |x| | | | |
| | |x| | | | | |
| |x| | | | | | |
|x| | | | | | | |











Ich habe die Bedingung entfernt, dass sich die Türme nicht vertikal treffen ... aber wie viel wird es jetzt sein? Muss 8 ^ 8 = 16777216 sein. Dann wurde mir klar, dass er lange brauchen würde, um zu zählen - wir unterbrechen den Prozess.

Es wurde auch eine schnelle Möglichkeit gefunden, 4 Lösungen abzuleiten, ohne alles zu berechnen - aus allem in derselben Bibliothek. Dies geschieht - wir finden eine Lösung, nach der wir sie zu den Ausnahmen der Booleschen Formel hinzufügen ...

Wie man mehrere Lösungen in Python ableitet
for cnter in range(4):
  sol = pycosat.solve(clauses)
  if isinstance(sol, list):
    print(sol)
    clauses.append([-x for x in sol]) #   -      
  else: #    - 
    return
#     ,    ,   .


Im Allgemeinen erhalten wir einige weitere Lösungen für das „Turmproblem“: 21:45 I: Im Allgemeinen ist alles klar, wenn überhaupt, jetzt kann ich es befestigen. Ich werde ein Buch und ein Spa lesen ... KREATIVER START: Nun, Sie verstehen, wie alles funktioniert. Und schreiben wir ein einfaches kleines Kreuzworträtsel mit japanischen Kreuzworträtseln! Nehmen Sie den einfachsten Fall - in jeder Zeile und Spalte gibt es nicht mehr als eine Ziffer (und dementsprechend einen Block). Sie suchen einfach nach den möglichen Optionen für die Zeile / Spalte. Hier geht es schnell und laden sie dann in den Solver. Lass es uns tun, oder? Ich: ( ohne einen Hinterhalt zu spüren ) Nun, komm schon ... es scheint, dass die Wahrheit nicht lange dauert ... Der Einfachheit halber nehmen wir nur NxN-Quadratkreuzworträtsel - gleichzeitig können Sie den Ausgabecode fertig stellen und überlegen, wo Horizontal und Vertikal nicht benötigt werden ...

| | | | | | | |x|
| | | | | | |x| |
| | | | | |x| | |
| | | | |x| | | |
| | | |x| | | | |
| |x| | | | | | |
| | |x| | | | | |
|x| | | | | | | |











22:10 Es funktioniert nicht: ((
Wir versuchen das 3x3-Kreuzworträtsel im 1-0-1-Format (bisher sind nur Blöcke horizontal, wir berücksichtigen keine Vertikalen). Es gibt alle leeren Zellen aus, Infektionen, berücksichtigt nicht einmal die gefüllten Blöcke ...

Okay, im Detail Wir beschäftigen uns mit dem Format der Datenübertragung zum Solver, und dann wird, gelinde gesagt, etwas Unerwartetes enthüllt ...

, «-» ?

/ /, — . , .

[1] 3 , :
[X__],[_X_],[__X]
, ,
[1,-2,-3],[-1,2,-3],[-1,-2,3]

, …
:
[1 -2 -3] [-1, 2...] [...]

, . , . — , !

Hinterhalt. Tatsächlich gibt es zwei Möglichkeiten - entweder um die Bedingungen für ein Kreuzworträtsel in KNF zu finden oder um herauszufinden, wie DNF in KNF konvertiert werden kann (nun, jemand hätte darüber nachdenken sollen, zumal beide Boolesche Formeln sind!)

22:50 Ich habe den Snack beendet. Ich habe nicht darüber nachgedacht, wie ich Bedingungen für KNF schaffen soll (ich habe später festgestellt, dass sie im Artikel beschrieben wurden , aber es war eine lange Zeit, sie zu programmieren, und ehrlich gesagt nicht Sport).

Wir suchen also einen Konverter von DNF zu CNF. Formale Beschreibungen von Transformationen im akademischen Stil sind im Internet verstreut ... warum brauche ich sie? Ich werde jetzt definitiv nicht bei ihnen bleiben, ich würde das Problem lösen. Wir sehen also die einzige (!) Python-Bibliothek mit dem entsprechenden Namen und den entsprechenden Funktionen. Jetzt werden wir uns verbinden ...

23:10Es funktioniert lustig - es macht zusätzliche Variablen und bringt durch sie DNF zu CNF. Offenbar ist es ziemlich schwierig, KNF ehrlich zu machen ...

Also funktioniert es auch nicht! Was ist los?
Wir senden die Testeingabe von DNF: lstall = [[1,2,3]]. Das heißt, wir erwarten, dass nur [1,2,3] (entspricht der vollständigen Zeile [XXX]) eine gültige Lösung ist. Die

Bibliothek gibt KNF an: cnf = [[-1, -2, -3, 4], [1 , -4], [2, -4], [3, -4]]
Es ist nicht klar, aber sehr interessant.

Nehmen wir an, ich glaube Ihnen ... Wir bitten Pycosat, alle Lösungen für cnf zu finden:
[1, 2, 3, 4]
[1, 2, -3, -4]
[1, -2, -3, -4]
[1 , -2, 3, -4]
[-1, -2, -3, -4]
[-1, -2, 3, -4]
[-1, 2, -3, -4]
[-1, 2, 3, -4]

Anstelle des erwarteten gibt es acht davon! Was zu tun ist?

, , 4 True. ?

: cnf2 = [[-1, -2, -3, 4], [1, -4], [2, -4], [3, -4], [4]]
pycosat cnf2:
1: [1, 2, 3, 4]

, , ! , , .
— !

23:10 Sie entscheidet! Hier ist ein 1-3-1-Kreuzworträtsel vertikal und horizontal: Hier ist eine doppelte Lösung eines 1-0-1-Kreuzworträtsels vertikal und horizontal: Ich: Also, alles funktioniert für mich, ich bin fertig. Wow, es ist schon 23 Uhr. Spa ... KREATIVER START: Verstehst du, dass du nur noch sehr wenig übrig hast, um japanische Kreuzworträtsel zu lösen? Es ist nur notwendig, eine rekursive Prozedur zu schreiben, die alle Zeilenoptionen nicht für einen Block, sondern für N ... Me: Nun ... es scheint einfach zu sein, ja ... 00:00 Um Mitternacht kocht der Kopf- Tupit bereits nicht so gut, aber ich debugge weiter Rekursion. Begriffe des japanischen Kreuzworträtsels - zwischen den Blöcken muss mindestens ein Leerzeichen stehen, es kann jedoch mehr als ein Leerzeichen geben.

| |x| |
|x|x|x|
| |x| |




1
| | |x|
| | | |
|x| | |
2
|x| | |
| | | |
| | |x|












Unter der Bedingung [1,2,1] für 6 Zellen sollte das Programm die einzige Option
[X_XX_X] generieren ,
und für 7 Zellen gibt es bereits 4 Optionen:
[X_XX_X_],
[X_XX__X],
[X__XX_X],
[_X_XX_X], Die

ganze Zeit in Ich berechne fälschlicherweise die Position von Zellen um eins und zum Teufel damit - es ist einfacher zu starten und zu reparieren als zu denken ...

00:30 Funktioniert es? Was noch zu überprüfen? Also, wo man eine maschinenorientierte Beschreibung von Kreuzworträtseln bekommt ... Auf dieser Seite, nein, auf dieser - nein ... Okay, zum Teufel damit, ich werde die Stifte unterbrechen. 00:50 Es funktioniert wirklich O_O Hier ist eine Krabbe mit jcrosswords.ru/crossword/65

3x3
rows = [[1,1],[0],[1,1]]
cols = [[1,1],[0],[1,1]]

:
|X| |X|
| | | |
|X| |X|

4x4
rows = [[1,1],[0],[0],[1,1]]
cols = [[1],[1],[1],[1]]

:
1
| |X| |X|
| | | | |
| | | | |
|X| |X| |
2
|X| |X| |
| | | | |
| | | | |
| |X| |X|







von meinem Löser entschieden. Die maximal getestete Größe beträgt 10x10, jetzt ist es sehr faul für mich, mehr Kreuzworträtsel in das Programm zu unterbrechen ... - Ich: Wow, es ist schon eines am Morgen! Warten Sie, es dauerte nur 4 Stunden ab dem Moment, als ich nichts über den SAT-Solver wusste? Also, wie war es überhaupt? (Es werden kurze Notizen darüber geschrieben, was passiert ist ...)

|x|_|x|_|_|_|_|x|_|x|
|x|x|x|_|_|_|_|x|x|x|
|_|x|_|_|_|_|_|_|x|_|
|_|x|_|x|_|_|x|_|x|_|
|_|x|x|x|x|x|x|x|x|_|
|_|_|x|x|x|x|x|x|_|_|
|x|x|x|x|x|x|x|x|x|x|
|_|_|x|x|x|x|x|x|_|_|
|x|x|_|x|x|x|x|_|x|x|
|x|_|_|_|_|_|_|_|_|x|








Nachwort.

Morgen . Auf Habr findet sich der Artikel über einen Löser auf Rust . Das Herunterladen von Kreuzworträtseln funktioniert nicht, aber nachdem wir den im Artikel angegebenen Github-Artikel gelesen haben , finden wir auf der Webpbn- Kreuzworträtsel-Website einen versteckten Link zum Exportieren von Kreuzworträtseln in einem maschinenlesbaren Format . Die Option „Format für Version 1 von Kyle Keen's Python-Solver“ ist für uns geeignet und entspricht unserer.

Wenn Sie Kreuzworträtsel nicht mit Stiften unterbrechen müssen, geht es schneller. Es wird schnell klar, dass das Maximum, das ein Löser lösen kann, Kreuzworträtsel im Bereich von 30 x 30 sind. Das Hauptproblem ist die Erzeugung von Varianten einzelner Linien, im Falle eines Haufens von Blöcken dieser Zustände SEHR viel und alles beginnt sich zu verlangsamen:

1. Erstens dauert das Generieren von Zeichenfolgenoptionen lange. Das Generieren aller Optionen für den String [2,1,3,1,4] der Länge 60 dauert bereits etwa eine halbe Minute ... (es gibt 2118760, wenn jemand interessiert ist)
2. Außerdem erzeugt die DNF-> CNF-Konvertierung zu viele Bedingungen und zusätzliche Variablen ...

Aber Wenn es nur wenige Blöcke gibt, können auch große Kreuzworträtsel in einer sinnvollen Zeit gelöst werden.

Katze - alles ist in Ordnung
Katze 20x20
51511 clauses...
2851 last var number
--- 0.05186057090759277 seconds for clauses gen ---
1
| | |X|X| | | | | | | | | | | | | | | | |
| |X|X| | | | | | | | | | | | | | | | | |
| |X| | | | | | | | | | | | | | | | | | |
| |X| | | | | | | | | | | | | | | | | | |
| |X| | | | | |X|X|X| | | | | | | | | | |
|X|X| | | | |X|X|X|X|X| | | | | | | | | |
|X| | | | |X|X|X|X|X|X|X| | | |X| | | |X|
|X| | | | |X|X|X|X|X|X|X|X| | |X|X| |X|X|
|X| | | |X|X|X|X|X|X|X|X|X| | |X|X|X|X|X|
|X|X| | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|
| |X| |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|
| |X|X|X|X|X|X|X| |X|X|X|X|X|X|X|X|X|X|X|
| | |X|X|X|X|X| | | |X|X|X|X|X| |X|X|X| |
| | |X|X|X|X|X| | | |X|X|X|X| | | | | | |
| | | |X|X|X| | | | | |X|X|X| | | | | | |
| | | |X|X| | | | | | |X|X| | | | | | | |
| | |X|X| | | | | | | | |X| | | | | | | |
| | |X| | | | | | | | | |X| | | | | | | |
| | |X|X| | | | | | | | |X|X| | | | | | |
| | |X|X| | | | | | | | |X|X| | | | | | |
--- 0.02073383331298828 seconds for solve ---


In dem Wunsch, mehr Kreuzworträtsel zu testen, nahm er schnell eine Ausrichtung vor - jedes Kreuzworträtsel wird auf ein Quadrat reduziert, wenn wir die verbleibenden Zeilen mit Nullen füllen. Ja, es ist nicht optimal, aber jetzt zögere ich, den Code zu wiederholen ...

Pferd - 30x38
Kreuzworträtsel 22
13622799 clauses... -
350737 last var number
--- 14.18206787109375 seconds for clauses gen ---
1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X| | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X| | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X| | | |
| | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X| |X|X| | |
| | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X|X|X|X| |
| | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|
| | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X| |X|X|X|X|X|
| | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X| | | | | |X| |
| | | | | | | | | | |X|X|X|X| | | | | | | | | |X|X|X|X|X|X|X|X| | | | | | | |
| | | | | | | | |X|X|X|X|X|X|X|X|X| | | |X|X|X|X|X|X|X|X|X|X| | | | | | | | |
| | | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | |
| | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | | |
| | | | |X|X|X| |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | | |
| | | |X|X|X| | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | | |
| | |X|X|X| | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | |
| |X|X|X| | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | |
|X|X|X|X| | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | |X|X| | | |
| |X|X| | | |X|X|X|X|X| | | | |X|X|X|X|X|X|X|X|X|X|X|X| | | | | | |X|X| | | |
| | |X| | | |X|X|X|X| |X| | | | | | |X|X|X|X|X|X| | | |X| | | | | |X|X| | | |
| | | | | |X|X|X|X| |X|X| | | | | | | | | | | | | |X|X|X| | | | | |X|X| | | |
| | | | |X|X|X|X| |X|X| | | | | | | | | | | | | | |X|X|X| | | | | |X|X| | | |
| | | | |X|X|X| | |X|X| | | | | | | | | | | | | | | |X|X| | | | | |X|X| | | |
| | | | |X|X| | | |X|X| | | | | | | | | | | | | | | |X|X| | | | |X|X|X| | | |
| | | |X|X|X| | | | |X|X| | | | | | | | | | | | | | |X|X| | | | |X|X| | | | |
| | | |X|X| | | | | |X|X| | | | | | | | | | | | | | |X|X| | | | |X| | | | | |
| | | |X|X| | | | | |X|X| | | | | | | | | | | | | | |X|X| | | | | | | | | | |
| | | |X|X| | | | | | |X|X| | | | | | | | | | | | | |X|X| | | | | | | | | | |
| | | |X|X| | | | | | |X|X| | | | | | | | | | | | | |X|X| | | | | | | | | | |
| | | | |X|X| | | | | | |X|X| | | | | | | | | | | | | |X|X| | | | | | | | | |
| | | | |X|X|X| | | | | |X|X|X| | | | | | | | | | | | |X|X|X| | | | | | | | |

--- 17.535892963409424 seconds for solve ---



In der Originalveröffentlichung zum SAT-Solver werden Bedingungen und Variablen ehrlicher generiert - daher ist die Lösung dort schneller und benötigt wahrscheinlich weniger Speicher. Wenn ich mich für ein Pferd entscheide, brauche ich ungefähr 2,7 GB RAM, und es beansprucht einige komplizierte Dinge für alle 12 installierten, und dann beginnen sie hektisch auf die Festplatte zu wechseln ...

Mehr aus den Schlussfolgerungen:

1. Mir wurde klar, warum am schnellsten gegoogelte Pycosat-Beispiele Sudoku-Lösungen sind. Sudoku-Regeln lassen sich viel einfacher auf den Solver anwenden, da Sie nicht die komplexe Beziehung zwischen Blöcken und Zellen beschreiben müssen, sondern nur die Bedingungen "jede Ziffer in jeder Zeile" und "nur eine Ziffer in einer Zeile".
Aber ich liebe Sudoku viel weniger, deshalb bereue ich die Entscheidung, die ich getroffen habe, nicht.

2. Mir hat gefallen, wie man DNF und CNF mischen kann. Wenn Sie nicht die richtigen Bedingungen schreiben möchten, generieren Sie nicht alle Optionen. Das ist natürlich nicht optimal, aber es funktioniert.

Natürlich habe ich keine Rekorde für die Geschwindigkeit beim Lösen von Kreuzworträtseln aufgestellt, aber ich hatte eine gute Zeit. Das Wichtigste ist, dass ich es schaffen kann! :) Was ich dir auch wünsche!

All Articles