Sudoku oplosser in Ruby
Hoihoi
Voor het eerst sinds een redelijke tijd weer eens een blogpost van me.
Dit keer over een sudoku oplosser die ik in het kader van een universiteitsopdracht (uva, MSc. software engineering) moest schrijven (don't ask....). Dit moest (helaas) in Ruby, en mocht dus niet in Haskell, wat ik een stuk handiger vind.
Solvertje is geschreven, en ik wilde zo min mogelijk brute-forcen. Uiteraard kon ik jullie mijn oplossing niet onthouden.
Het idee is dat er een schaduwmatrix wordt gemaakt (pfields) van de gewone sudoku matrix (fields) met daarin per veld alle nog mogelijke waarden.
Zolang het mogelijk is om een getal in de gewone matrix in te vullen ) is er dus een pfields met als lengte 1 (en als inhoud het getal dat in fields moet).
Telkens als ik een waarde toevoeg aan fields wordt die pfields matrix geupgrade.
Als er geen waardes meer zijn die ik kan invullen gaat het algoritme kijken welk pfields veld de kortste lengte heeft (en dus de minste invulmogelijkheden) en dan worden verschillende instances van de sudoku gespawned (zie de fork methode) om zo verder te gaan met solven. Hier gebruik ik dus backtracking; termineert een van de sudoku-forks echter positief, dan is de sudoku sowieso opgelost.
Ik heb even een en ander van blackboard gecopieerd als voorbeeld code:
code:
Deze sudoku wordt binnen de seconde opgelost door mijn Intel SU9400 in mijn laptopje
.
Het gaat hier om ongeveer 500 regels nuttige code:
code:
En ik ben me ervan bewust dat sommige dingen best makkelijker kunnen (zie ook het commentaar in sudoku.rb).
runner.rb is het bestand waarmee je de rest runt, en die gebruikt voorbeeldsudoku om de voorbeelden uit te halen. Momenteel is daar bovenstaande sudoku voor ingesteld.
de logger en de printer zijn handig voor debuggen, en sudoku.rb is de logica zelf.
Files zijn hier te vinden:
http://devcorner.boudewijnector.nl/runner.rb
http://devcorner.boudewijnector.nl/stdoutPrinter.rb
http://devcorner.boudewijnector.nl/sudokuLogger.rb
http://devcorner.boudewijnector.nl/sudoku.rb
http://devcorner.boudewijnector.nl/voorbeeldsudoku.rb
Heb je feedback of kritiek, dan hoor ik het graag!
Voor het eerst sinds een redelijke tijd weer eens een blogpost van me.
Dit keer over een sudoku oplosser die ik in het kader van een universiteitsopdracht (uva, MSc. software engineering) moest schrijven (don't ask....). Dit moest (helaas) in Ruby, en mocht dus niet in Haskell, wat ik een stuk handiger vind.
Solvertje is geschreven, en ik wilde zo min mogelijk brute-forcen. Uiteraard kon ik jullie mijn oplossing niet onthouden.
Het idee is dat er een schaduwmatrix wordt gemaakt (pfields) van de gewone sudoku matrix (fields) met daarin per veld alle nog mogelijke waarden.
Zolang het mogelijk is om een getal in de gewone matrix in te vullen ) is er dus een pfields met als lengte 1 (en als inhoud het getal dat in fields moet).
Telkens als ik een waarde toevoeg aan fields wordt die pfields matrix geupgrade.
Als er geen waardes meer zijn die ik kan invullen gaat het algoritme kijken welk pfields veld de kortste lengte heeft (en dus de minste invulmogelijkheden) en dan worden verschillende instances van de sudoku gespawned (zie de fork methode) om zo verder te gaan met solven. Hier gebruik ik dus backtracking; termineert een van de sudoku-forks echter positief, dan is de sudoku sowieso opgelost.
Ik heb even een en ander van blackboard gecopieerd als voorbeeld code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| De volgende testcase kun je gebruiken om je Sudoku sovler uit te proberen. --- | --- | -1- 4-- | --- | --- -2- | --- | --- ----+-----+---- --- | -5- | 4-7 --8 | --- | 3-- --1 | -9- | --- ----+-----+---- 3-- | 4-- | 2-- -5- | 1-- | --- --- | 8-6 | --- Deze Sudoku is notoir lastig. Dit houdt in dat deze bewijsbaar tot minimaal 17 levels backtracking zal leiden bij een triviale brute-force approach. Om de performance van je solver in orde te krijgen moet je dus nog iets slims doen, bijvoorbeeld een goede heuristiek gebruiken voor de volgorde waarin je door de Sudoku werkt. Tip: vermijdt voortijdige optimalisatie en maak aanvankelijk een cleane versie waarvan je daarna de ordegroottes van je algorithme goed kunt krijgen. |
Deze sudoku wordt binnen de seconde opgelost door mijn Intel SU9400 in mijn laptopje
Het gaat hier om ongeveer 500 regels nuttige code:
code:
1
2
3
4
5
6
7
| www-productie:/var/www/boudewijnector.nl/devcorner# wc -l * 29 runner.rb 93 stdoutPrinter.rb 155 sudokuLogger.rb 481 sudoku.rb 141 voorbeeldsudoku.rb 899 total |
En ik ben me ervan bewust dat sommige dingen best makkelijker kunnen (zie ook het commentaar in sudoku.rb).
runner.rb is het bestand waarmee je de rest runt, en die gebruikt voorbeeldsudoku om de voorbeelden uit te halen. Momenteel is daar bovenstaande sudoku voor ingesteld.
de logger en de printer zijn handig voor debuggen, en sudoku.rb is de logica zelf.
Files zijn hier te vinden:
http://devcorner.boudewijnector.nl/runner.rb
http://devcorner.boudewijnector.nl/stdoutPrinter.rb
http://devcorner.boudewijnector.nl/sudokuLogger.rb
http://devcorner.boudewijnector.nl/sudoku.rb
http://devcorner.boudewijnector.nl/voorbeeldsudoku.rb
Heb je feedback of kritiek, dan hoor ik het graag!
|
|
Zelfbouw-waterkoeling: radiator-behuizing |
Reacties
Hoe reageert 'ie als een ingevoerd probleem meerdere oplossingen heeft? Stopt hij bij de eerst gevonden oplossing of laat hij alle mogelijke oplossingen zien?
(yep: ik ben te lui en heb niet de programmeerervaring op het even zelf te proberen...)
(yep: ik ben te lui en heb niet de programmeerervaring op het even zelf te proberen...)
Wel leuk om te doen , zelf ben ik op de Uni bezig met het leren van haskell wat backtracking een stuk makkelijker (en minder code ) maakt dan met java . Alleen is de leesbaarheid wat minder 
500 regels? Ik heb het wel eens in 1 regel gezien,
. Dat was volgens mij Ruby of Python, een van de twee. Een wiki-pagina daarvan werkt helaas niet meer en/of staat vol met spam, maar de code is als volgt:
code:
Ik meen toch echt dat d'r eentje was die eenvoudiger was, maar het kan zijn dat ik me vergis.
Ik heb sowieso het idee dat je oplosser een stuk korter kan (zie ook backtracking), maar da's niet altijd het belangrijkste.
code:
1
2
3
4
| def r(a):i=a.find('0');~i or exit(a);[m
in[(i-j)%9*(i/9^j/9)*(i/27^j/27|i%9/3^j%9/3)or a[j]for
j in range(81)]or r(a[:i]+m+a[i+1:])for m in'%d'%5**18]
from sys import*;r(argv[1]) |
Ik meen toch echt dat d'r eentje was die eenvoudiger was, maar het kan zijn dat ik me vergis.
Ik heb sowieso het idee dat je oplosser een stuk korter kan (zie ook backtracking), maar da's niet altijd het belangrijkste.
@Yopy
Tja man, beetje logische dat bruteforce / backtracking in weinig code geklopt kan worden (leuk voorbeeld daarin
). Het kost waarschijnlijk alleen veel meer rekenen. Niet dat we daar echt een probleem mee krijgen.
Ik ben in het verleden zelf bezig geweest met een technisch / tactisch spelende sudoku-software. Niet dat ik alle deducties zelf al kan uitvoeren. De lap code zou groter zijn in ruil voor snelheid.
Tja man, beetje logische dat bruteforce / backtracking in weinig code geklopt kan worden (leuk voorbeeld daarin
Ik ben in het verleden zelf bezig geweest met een technisch / tactisch spelende sudoku-software. Niet dat ik alle deducties zelf al kan uitvoeren. De lap code zou groter zijn in ruil voor snelheid.
Voor dit soort dingen is een declaratieve taal zoals in ECLiPSe (http://en.wikipedia.org/wiki/ECLiPSe) ideaal.
Ik heb de voorbeeldsudoku ook even door mijn solver gehaald en die weet hem ook op te lossen in 250 ms (P4 2.8). Mijn solver gebruikt puur strategieën die je normaal op papier ook zou gebruiken. Forken hoort daar niet bij 
Ik heb even gekeken naar welke strategieën mijn oplosser gebruikt heeft. Dat zijn er twee geweest.
1. Paired Cell Strategy: Er bestaan op een rij twee cellen waarin alleen de mogelijkheden 6 en 9 nog open zijn. Je kan dan in de andere cellen in die rij de 6 en 9 wegstrepen uit de mogelijkheden. Bijv als volgt:
code:
Daar kunnen de 6 en 9 dus weg uit de laatste cel, waardoor daar allen nog de 8 staat. Het paar kan ook 'verstopt zijn'. Dat is bijvoorbeeld hieronder het geval:
code:
Je kan daar in cel 3 de 7 wegstrepen, omdat de 6 en 9 in cel 3 en 8 moeten en nergens anders kunnen. De 7 past daar dus niet.
2. Cell Overlap Strategy: Je maakt gebruik van de opverlap tussen rijen en kwadranten (3x3 vakken). Voorbeeld uit jouw sudoku:
code:
Je ziet hier de vierde rij en het vierde kwadrant (linksmidden) volledig weergegeven met alle opties die op dat punt in het oplossen nog beschikbaar waren. Je ziet dat de 9 in de rij alleen in het vierde kwadrant kan liggen. Dat betekent dat je de 9 op de andere plaatsen in dat kwadrant weg kan halen:
code:
Misschien heb je er wat aan, misschien hoort het niet bij je opdracht
Ik ben de oplosser begonnen met alleen maar de simpele strategieen (1 optie in een cel of 1 mogelijke plaats in een groep cellen). Daarna heb ik hem puzzels laten oplossen tot zo ver hij kwam. Die uitkomsten ben ik toen met de hand gaan oplossen en ik heb de oplosmethode gegeneraliseerd en geïmplementeerd in mijn oplosser. Vervolgens weer puzzels laten oplossen en weer met de hand aangevuld en geïmplementeerd. Dit alles totdat mijn gebrek aan programmeerskills mij weerhield van het oplossen van 6-sterren puzzels.
Mijn uitdaging is nog steeds om 'Killer Sudokus' op te gaan lossen.
Ik heb even gekeken naar welke strategieën mijn oplosser gebruikt heeft. Dat zijn er twee geweest.
1. Paired Cell Strategy: Er bestaan op een rij twee cellen waarin alleen de mogelijkheden 6 en 9 nog open zijn. Je kan dan in de andere cellen in die rij de 6 en 9 wegstrepen uit de mogelijkheden. Bijv als volgt:
code:
1
| 3 1 (6, 9) 4 7 5 2 (6, 9)(6, 8, 9) |
Daar kunnen de 6 en 9 dus weg uit de laatste cel, waardoor daar allen nog de 8 staat. Het paar kan ook 'verstopt zijn'. Dat is bijvoorbeeld hieronder het geval:
code:
1
| 3 1 (6, 9, 7) 4 (7, 8) (7, 5) 2 (6, 9) (5, 7, 8) |
Je kan daar in cel 3 de 7 wegstrepen, omdat de 6 en 9 in cel 3 en 8 moeten en nergens anders kunnen. De 7 past daar dus niet.
2. Cell Overlap Strategy: Je maakt gebruik van de opverlap tussen rijen en kwadranten (3x3 vakken). Voorbeeld uit jouw sudoku:
code:
1
2
3
| (2, 6, 9)(3, 6, 9)(2, 3, 6, 9) (2, 3, 6) 5 1 4 8 7 (2, 5, 6, 7, 9)(4, 6, 7, 9) 8 (2, 5, 6, 7)(3, 4, 6, 7) 1 |
Je ziet hier de vierde rij en het vierde kwadrant (linksmidden) volledig weergegeven met alle opties die op dat punt in het oplossen nog beschikbaar waren. Je ziet dat de 9 in de rij alleen in het vierde kwadrant kan liggen. Dat betekent dat je de 9 op de andere plaatsen in dat kwadrant weg kan halen:
code:
1
2
3
| (2, 6, 9)(3, 6, 9)(2, 3, 6, 9) (2, 3, 6) 5 1 4 8 7 (2, 5, 6, 7)(4, 6, 7) 8 (2, 5, 6, 7)(3, 4, 6, 7) 1 |
Misschien heb je er wat aan, misschien hoort het niet bij je opdracht
Ik ben de oplosser begonnen met alleen maar de simpele strategieen (1 optie in een cel of 1 mogelijke plaats in een groep cellen). Daarna heb ik hem puzzels laten oplossen tot zo ver hij kwam. Die uitkomsten ben ik toen met de hand gaan oplossen en ik heb de oplosmethode gegeneraliseerd en geïmplementeerd in mijn oplosser. Vervolgens weer puzzels laten oplossen en weer met de hand aangevuld en geïmplementeerd. Dit alles totdat mijn gebrek aan programmeerskills mij weerhield van het oplossen van 6-sterren puzzels.
Mijn uitdaging is nog steeds om 'Killer Sudokus' op te gaan lossen.
Ik weet,quote: fl!pull@Yopy
Tja man, beetje logische dat bruteforce / backtracking in weinig code geklopt kan worden (leuk voorbeeld daarin ). Het kost waarschijnlijk alleen veel meer rekenen. Niet dat we daar echt een probleem mee krijgen.
Ik was eerst begonnen met eentje die het zoals ik oploste (kijken naar wat zijn de enige mogelijkheden in rij / kolom / vak en nog eenzelfde die alles wat gewoon niet mogelijk was wegkraste). Die kon ook een groot deel van de sudoku's oplossen (tot 'medium', soms 'hard' op een of andere website).
Voor de mensen die uit het hoofd alles kunnen:
Hoe wil je die voorbeeldsudoku dan doen? Mijn docent beweert eea over minstens 17 niveaus
.
Hoe wil je die voorbeeldsudoku dan doen? Mijn docent beweert eea over minstens 17 niveaus
Ik heb hem net even uit mn hoofd opgelost, ik wil niet doen alsof ik 1 of andere sudoku koning ben, maar hij viel opzich best mee, je kan heel veel getallen met de meest basis regels vinden, bijvoorbeeld alle 1 heb je aan het begin als het goed is al.
code:
code:
1
2
3
4
5
6
7
8
9
10
11
| 693 | 784 | 512 487 | 512 | 936 125 | 963 | 874 ----+-----+---- 932 | 651 | 487 568 | 247 | 391 741 | 398 | 625 ----+-----+---- 319 | 475 | 268 856 | 129 | 743 274 | 836 | 159 |
Netjes, maar ik neem aan dat je best wel even bezig bent geweest?
En je hebt daar alleen regels gebruikt of ben je ook aan het backtracken geweest?
En je hebt daar alleen regels gebruikt of ben je ook aan het backtracken geweest?
Ik ben er geloof ik 20 minuten mee bezig geweest. Ik heb geen lijst met mogelijke nummers gemaakt, dat vind ik altijd zo lame
probeer dat altijd in mijn hoofd te doen.
Ik heb niet echt een officiële benaming voor al mijn " regels ". Dingen die ik doe zijn:
- er is maar 1 mogelijkheid, door het blok, door de rijen of de kolommen. daarmee kon ik bijvoorbeeld alle 1en in dit ding al vinden.
- als op 1 of andere manier ergens 2 cijfers in de zelfde kolom / rij alleen maar op die plek kunnen, gebruik ik dat om in de rest van de kolom/ rij de cijfers te elimineren... met dit mooie voorbeeldje moet dat wel duidelijk zijn:
code:
Waarbij die regel vele vormen kent.
- als een blok bijna vol is, maar 1 hele rij van het blok nog niet, die cijfers mogen niet meer in de rest van de rij.
Klinkt misschien allemaal wat vaag, maar zo heb ik ze uitgevonden, en ze werken prima
Ik heb niet echt een officiële benaming voor al mijn " regels ". Dingen die ik doe zijn:
- er is maar 1 mogelijkheid, door het blok, door de rijen of de kolommen. daarmee kon ik bijvoorbeeld alle 1en in dit ding al vinden.
- als op 1 of andere manier ergens 2 cijfers in de zelfde kolom / rij alleen maar op die plek kunnen, gebruik ik dat om in de rest van de kolom/ rij de cijfers te elimineren... met dit mooie voorbeeldje moet dat wel duidelijk zijn:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 1-5 | --- | --- 2-- | --- | --- 3-4 | --- | --- ----+-----+---- 5-2 | --- | --- --- | -6- | --- <- geen 6 verder op deze rij 4-3 | --- | --- ----+-----+---- --- | --- | --- --- | --- | --- --- | --- | --- ^ 6 ergens op deze rij in het middelste blok ^ enige plek in de bovenste waar nog een 6 past :) |
Waarbij die regel vele vormen kent.
- als een blok bijna vol is, maar 1 hele rij van het blok nog niet, die cijfers mogen niet meer in de rest van de rij.
Klinkt misschien allemaal wat vaag, maar zo heb ik ze uitgevonden, en ze werken prima
is dit een opdracht uit een mastervak? klinkt meer als inleiding programmeren ofzo
Het is een opdracht waar nog wat extra's bij zit, het vak gaat over testing trouwens.
Ja het is een mastervak, en ik ben master-student.
Ja het is een mastervak, en ik ben master-student.
Hier nog een leuke sudoku om je solvertje mee te testen
002 | 090 | 107
038 | 600 | 000
400 | 000 | 000
----+-----+----
000 | 005 | 000
009 | 010 | 300
000 | 400 | 000
----+-----+----
000 | 000 | 004
000 | 007 | 920
806 | 030 | 700
002 | 090 | 107
038 | 600 | 000
400 | 000 | 000
----+-----+----
000 | 005 | 000
009 | 010 | 300
000 | 400 | 000
----+-----+----
000 | 000 | 004
000 | 007 | 920
806 | 030 | 700
Op http://scanraid.com/sudoku.htm vindt je heel veel oplossingsstrategieen. Mijn oplosser heeft volgens mij alleen de eerste 7 en die kan jouw sudoku oplossen.
Ik heb dat ooit eens geprobeerd, maar destijds waren mijn programmeer skills niet voldoende, icm mijn motivatie, om tot iets te komen.
Maar leuk project!
Ik heb me overigens altijd afgevraagd of een sudoku 'analytisch' opgelost kan worden. i.e. een formule oid op te stellen die de complete matrix uitspuugt. De reden daarvoor is dat een sudoku nogal regelgebonden is, met een bekende blok structuur.
Maar leuk project!
Ik heb me overigens altijd afgevraagd of een sudoku 'analytisch' opgelost kan worden. i.e. een formule oid op te stellen die de complete matrix uitspuugt. De reden daarvoor is dat een sudoku nogal regelgebonden is, met een bekende blok structuur.
Daardoor kun je hem toch analytisch oplossen? Je kunt hier zo een Z of Alloy specificatie voor schrijven (3x raden wat nog meer deel was van de opdracht
).