Sudoku megoldó program

Az előző bejegyzésben mátrixok előállításáról és ezekkel kapcsolatos műveletek végző függvények készítéséről volt szó. Most alkalmazási példaként a sudoku játékot megoldó programot írunk. Bár a sudokut vélhetően mindenki ismeri, de a feladatspecifikációhoz most röviden összefoglaljuk.

A sudoku egy olyan logikai játék, melyben egy 9×9-es táblázat van 9 darab 3×3-as résztáblázatra felosztva, és minden résztáblázatot a nullánál nagyobb egyjegyű egész számokkal kell kitölteni úgy, hogy nem csak minden egyes résztáblázatban, hanem az egész 9×9-es táblázat minden egyes sorában és minden oszlopában az 1..9 számok mindegyike csak egyszer fordulhat elő. A játék egy olyan kezdőtáblázattal indul, amelyben bizonyos cellákban már szerepelnek számok.  A feladat, hogy a maradék cellákat kell kitölteni az előbb írt feltételeknek megfelelően.

A sudoku programunkban a táblázatot mint mátrixot az előző bejegyzésben látottakhoz hasonlóan egy olyan listával modellezzük, amelyben a sorokat 9 darab 9 elemű lista képviseli.

A megoldás menete a következő lesz:

  1. Megkeressük a következő üres cellát a sudoku mátrixban. (Tehát azt, amelyben 0 érték szerepel)
  2. Ha nem találunk üres cellát, akkor meg van oldva a rejtvény, mert a következőkben ismertetett lépésekben mindig csak a szabályok szerint érvényes értéket írunk egy üres cellába.
  3. Ha találunk üres cellát, akkor megpróbáljuk egy megfelelő értékkel kitölteni.
  4. Ehhez vesszük sorban az 1..9 számokat.
  5. Ellenőrizzük, hogy az aktuális szám egy lehetséges, érvényes megoldás-e.
  6. Ha igen, azaz az aktuális szám egy érvényes szám, akkor az eddig üres cella értékét erre írjuk át.
  7. Most van egy egyetlen számban módosított sudoku mátrixunk. Erre kell a fenti lépéseket újra elvégezni, tehát folytatjuk az 1. lépéssel. Vagyis rekurzívan oldjuk meg a feladatot.
  8. Egy új lehetséges számmal próbálkozunk a 4. lépéshez visszatérve, ha
    • az aktuális szám nem lehet érvényes érték, vagy
    • a rekurzív lépésekben kiderül, hogy egy szám sem felel meg. Ekkor a korábban átírt cellát visszaállítjuk üresnek.
  9. Ha a 4. lépés számait sorra véve egyik sem bizonyul megfelelőnek, akkor a feladatnak nincs megoldása.
  10. A helyesen megoldott táblázatot megjelenítjük.

Látható, hogy az algoritmus elvi alapja, hogy próbálkozunk, aztán ha nem jön be, akkor ezt elvetjük, azaz visszalépünk és egy másik úton-módon keressük a megoldást. E jellege miatt ezt visszalépéses keresési (backtracking) algoritmusnak nevezik.

A sudoku megoldó program egy lehetséges változatát láthatjuk alább.

Az első függvény a mátrixban megkeresi a következő 0 értékű cellát, és ha van, akkor annak sor- és oszlopindexével tér vissza. Ha ilyen nem található, akkor None értékeket tartalmazó tuple lesz az eredmény.

A második függvény ellenőrzi, hogy a sudoku szabályai szerint egy indexekkel megadott cellába a szintén argumentumként adott számérték megfelelő megoldás lehet-e. A függvény először a sorban, valamint az oszlopban való megfelelést vizsgálja. Ha ott nincs kizáró ok, akkor következik, hogy megnézzük, hogy abban a kis 3×3-as résztáblázatban, amelybe a cella beleesik szintén áll-e a megfelelőség.

E két függvényben a sor- és oszlopindexek iterálását egymásba ágyazott for-ciklusok helyett az itertools modul product() függvényével végezzük, ami talán egy kicsit jobban olvashatóvá teszi a kódot. Az egymásba ágyazott for-ciklusok lényegében az indexek Descartes-szorzatát képezik. Ugyanezt teszi a product() függvény is.

A harmadik függvény végzi a feladat megoldását a fent sorolt lépések szerint.

A kommentek mindhárom függvénynél segítik a megértést.

A tesztelés során egy karakterláncban kapjuk folytatólagosan a megoldandó táblázat sorértékeit, ahol a 0 jelenti a kitöltendő üres cellát.

Ebből egy korábbi bejegyzésben szereplő módon 9 karakter hosszú számsor darabokat készítünk és ezekből építjük fel a sudoku mátrixot. /Az, hogy a sorok számértékei kezdetben egy karakterláncban vannak azt is lehetővé teszi, hogy akár szövegfájlból is könnyen beolvashatjuk e karaktersorozatot./

A megoldandó táblázatot megjelenítjük az előző, mátrixokról szóló bejegyzésben szereplő mátrix_string() függvény igénybevételével. Ennek kódját itt most nem ismételjük meg.

A feladatot megoldatjuk a sudoku_megoldás() függvénnyel, majd a megoldott, kitöltött táblázatot megjelenítjük.

Ebben a feladatban a ciklus, a listaépítő kifejezés és a rekurzió, illetve rekurzív függvény képezte a lényeget. Ezekről részletesen a Python tudásépítés lépésről lépésre című e-könyv alábbi fejezeteiben van szó: „Műveletek” fejezet – „Különleges műveletek” alfejezet – „Konténerépítés” alcím „Repetázzunk – ciklikus utasítás végrehajtás” fejezet, „Különleges függvénydefiníciók” fejezeten belül az „Amikor a kígyó a farkába harap – függvényrekurzió” alfejezet.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.