Sudoku's in PerlIn 2006 was ik op reis, en had wat tijd over (reizen is soms voor een groot deel wachten, zoals op vliegvelden). In de lunchbox van een eerdere vlucht zaten twee Sudoku's, die semi-Japanse cijferpuzzels. Ik maak die wel eens 'met de hand', en dacht: hoe moeilijk zou het zijn om zo'n puzzel op de computer op te lossen? Ik weet wat strategieën, kan ik die niet in een programma omzetten? Zo gezegd, zo gedaan... Tenminste, zo begonnen. Voor je je afvraagt 'is dit nu leuk': uiteraard ga ik niet met zo'n programma voor mijn plezier andere Sudoku's oplossen, dat doe ik nog steeds met de hand. Maar, mijn uitdaging was om te kijken of ik inderdaad een strategie wist te bedenken om elke Sudoku op te lossen. Dit is een goede stap in die richting (alle normale Sudoku's kan het volgens mij aan). Opdracht geslaagd, en programma in het archief; de andere Sudoku's doe ik wel met de hand... Het ideeHier een Engelse site met uitleg van vreselijk veel verschillende strategieën voor het oplossen van Sudoku's (plus on-line Sudoku's oplossen. Bedankt Wobien! Nu is programmeren voor een groot deel nadenken. Wat is het probleem (da's hier duidelijk), hoe kan je het oplossen (je strategie -> algoritme), en hoe sla je de gegevens op? Het laatste is vaak een van de sleutels tot een goed programma. Wat is het kernprobleem met Sudoku's: je hebt een bord met vakjes. Vakjes kunnen op verschillende manieren bij groepen ingedeeld worden (rijen, kolommen, en zo), en kunnen dus in meerdere groepen voorkomen. In elke groep mag vervolgens elk cijfer (of symbool) maar een keer voorkomen. Bij standaard-Sudoku's zijn die groepen bijvoorbeeld de rijen, kolommen, en de 3x3 vlakjes zoals normaal in de puzzel aangegeven. Soms zijn er ook extra regels, zoals de groepen gevormd door de diagonalen (de diagonaal-Sudoku), of worden er in plaats van 3x3 vakjes willekeuriger vormen gebruikt (vorm-Sudoku). We hebben dus:
De dataopslag (data structures)Hoe sla ik dit op? Het bord deel ik op in vakjes, die ik bij een 9x9 bord
nummer van 0 tot 80 (programmeurs doen het vanaf 0). Dit wordt in het programma
voorgesteld door een normaal array (genaamd Voor de regels gebruik ik lijsten (implementatie als array) waarin ik
bijhoud welke cellen in de groep van die regel zitten. Bijvoorbeeld de eerste
rij is dan (0,1,2,3,4,5,6,7,8), de eerste kolom (0,9,18,27,36,45,54,63,72). Al
deze regels verzamel ik in een array (genaamd De 9 rijen kan ik in Perl in een keer als 9 lijsten in array
for my $j (0..8) { push @rules, [ map { $j*9 + $_ } (0..8) ] }; Op soortgelijke manier kan ik ook de kolommen, en met wat meer gepruts de 3x3 gebieden, maken. Daarnaast nog wat administratie, zoals een variabele Strategie 1Mijn eerste strategie is simpel (en effectief). In de opgave zijn een aantal cellen gegeven. Deze waardes kunnen dus niet in de andere cellen van iedere groep voorkomen (in iedere groep mag elk symbool maar een keer voorkomen). Voor iedere groep waar deze cel in zit kan dus deze mogelijke waarde uit de andere cellen weggestreept worden (zie voorbeeld: als de '9' in cel 3 zit, ik tel vanaf 0, dan kan de 9 als mogelijkheid uit die kolom, die rij en dat 3x3 vak weggestreept worden: op de rode lijnen kan geen 9 staan). Met een beetje mazzel (bij eenvoudige Sudoku's) levert dit weer nieuwe
cellen op waar nog maar een mogelijkheid over is. Door dan weer dezelfde
strategie toe te passen is er nog meer weg te strepen. En zo door, tot er
mogelijk een oplossing overblijft. In pseudo-programmeertaal (in de Perl source
is dit de sub ' Zolang we nog/weer nieuwe cellen met een unieke waarde hebben
doen we: Dit blijkt heel aardig te werken. Een simpele (3-sterren Sudoku) bleek met deze methode gelijk opgelost te worden. En eenvoudig te programmeren, binnen twee uur typen/testen had ik dit geprogrammeerd en werkend (maar, ik had dus wel al voor die twee uur al een goed beeld van wat en hoe...). Strategie 2Helaas, voor moeilijker Sudoku's bleek dit niet afdoende te zijn. Wat weet ik nog meer voor strategieën? Een aanvullende strategie is: als er binnen een groep een cel is met meerdere mogelijkheden, waarvan een mogelijkheid alleen maar in die cel van de groep voorkomt, dan moet die mogelijkheid ook de enige voor die cel zijn. Klinkt moeilijk, maar kijk het voorbeeld hiernaast: als er in een groep drie vakjes zijn met de mogelijkheden 421, 21, 21 (even aannemende dat in de andere vakjes van de groep ook geen 4 voorkomt), dan kan de 4 alleen nog maar in dat 421 vakje staan, en kan dat vakje dus geen 1 of 2 bevatten. Door dus de 4 alleen in deze cel te zetten (de 1 en de 2 te wissen) hebben we een nieuwe unieke waarde, en kunnen we strategie 1 weer toe gaan passen. Op een 5-ster Sudoku blijkt dit inderdaad het veld verder op te ruimen! Weer het algoritme is pseudo-code (in het Perl programma is dit de sub
' Zolang we nog nieuwe waarden vinden doen we: Ook weer relatief simpel, voor een mens. De werkelijke implementatie voor een computer is nog wel even denken, een simpel zinnetje als 'Als dit symbool maar in een cel van de groep voorkomt' is nog lastiger dan je zou verwachten. Wat ik doe is voor alle cellen in de groep, behalve de cel waarmee we bezig zijn) de patronen met een bitwise 'or' -functie combineren. Op die manier hebben we in een waarde alle symbolen die in die cellen voorkomen. Als we nu alle bits omdraaien hebben we de symbolen die juist niet in die cellen voorkomen. Door nu een bitwise 'and'-functie toe te passen met de cel die we aan het bekijken zijn, houden we die symbolen over die niet in andere cellen maar wel in deze cel voor komt. Met een beetje mazzel is dat er een, hebben we pech dan is dat er geen; zijn het er meer dan een dan klopt er echt iets niet... Maart 2009: een nieuwe strategie (nummer 3) toegevoegd; zie verderop op deze pagina Strategie 9999Helaas, een 5-sterren Sudoku draait nog steeds niet... Dan weet ik nog maar
een strategie: brute kracht... vandaar nummer 9999: als deze het niet vind, dan
vind niets het (ultieme maar botte strategie). Hopelijk hebben de twee eerste
strategieën al heel wat weggestreept (en we kunnen ze later nog vaker
toepassen). Wat is de brute kracht methode? In principe heel simpel: voor
iedere cel waar nog meerdere waardes mogelijk zijn gaan we ieder van die
waardes gewoon proberen. Dus, weer een stukje pseudo-code (deze keer van de
functie ' Zolang we nog nieuwe waarden vinden doen we: Dit blijkt te werken!! Let op: we maken hier gebruik van 'recursie': deze functie kan zichzelf aanroepen (strategie 9999 gebruikt ook strategie 9999). Maar, omdat de cel waarmee we bezig waren op dat moment nog maar een waarde bevat (de waarde die we aan het proberen zijn), zijn er dus altijd minder mogelijkheden dan waarmee we begonnen, en zal de recursie altijd ophouden: of we vinden een oplossing, of we hebben geen mogelijkheden meer. En gezien we met deze methode automatisch alle mogelijkheden afgaan, moeten we bij een geldige Sudoku dus altijd het antwoord vinden. En jawel, ook de 5-sterren Sudoku is nu in 2 seconden opgelost!
Invoer en uitvoerHoe krijgen we een Sudoku naar binnen? En het resultaat naar buiten? Een Sudoku is gelukkig niet zo veel typewerk, uiteindelijk zijn het maar 9x9 vakjes. Ik heb gekozen om de input via een bestand te doen, geeft ook de mogelijkheid eventuele extra regels toe te voegen (zie volgende hoofdstuk). zie hier de input file van de boven besproken 3 sterren Sudoku: # 3-star sudoku for testing Afdrukken gaat in een soortgelijk formaat, zie hiernaast. Denk er aan; Perl is standaard een script-taal uitgevoerd via de DOS-prompt ofwel CMD window; ik heb er geen grafische user interface omheen gebreeën. Wat je hiernaast ziet is een screen capture van de output: de oplossing van de hierboven genoemde 3-sterren Sudoku. Extra beperkingen -> nieuwe regelsHet is ook mogelijk nieuwe regels toe te
voegen. Zo zijn er bijvoorbeeld Sudoku's met extra voorwaarden, bijvoorbeeld
dat in extra gebieden de symbolen ook maar een keer mogen voorkomen. Dit kan op
de diagonalen zijn, of in andere gebieden zoals de rode vakken in het figuur
hiernaast. Dit kan mijn programma ook aan, door extra regels in de inputfile op
te nemen (zie bijvoorbeeld de file ' Dit gebeurd door voor die gebieden de lijst met de nummers van de vakjes toe
te voegen aan de bestaande lijst. De syntax hiervoor is vrij simpel:
onderstaande regels komen uit # New constraints: 4 extra squares Het programma
|
op mijn site |