HW02: Sudoku

Odevzdávání úkolu je ukončeno.
Autor zadání Ondřej Šebek
Úprava Lukáš Ručka Ota Mikušek Tomáš Vondrák
Odevzdávané soubory sudoku.c
Začátek odevzdávání viz diskusní fórum
Další odevzdání navíc do 2024-03-20 24:00
Konec odevzdání 2024-03-27 24:00
Vzorová implementace /home/kontr/pb071/hw02/sudoku
Opravy v zadání 2024-03-23 21:48 4

Představení úkolu

V této úloze vás nečeká nic menšího, než řešení hlavolamu známého jako sudoku. Mnozí z vás si s ním již jistě zkusili poměřit síly, a pravděpodobně na něj na univerzitě ještě narazíte i v dalších předmětech.

Tentokrát nás ale čeká jen dílčí část celého problému - a to dosazení jednoznačných možností. V tomto úkolu budete implementovat malou knihovnu funkcí, které budou napomáhat k nalezení řešení.

Zadání

Sudoku je logická hra, při které hledáme takové rozmístění číslic (jedna až devět) do nevyplněných políček tak, aby v každém sloupci, řádku a (zvýrazněném) čtverci 3x3 byla každá číslice právě jednou. Tedy snažíme se najít takové doplnění, které nám z nevyřešeného sudoku (vlevo) dá jeho řešení (vpravo).

+=======+=======+=======+       +=======+=======+=======+
| . . . | 4 5 6 | 7 8 9 |       | 1 2 3 | 4 5 6 | 7 8 9 |
| . . . | 1 2 3 | 4 5 6 |       | 7 8 9 | 1 2 3 | 4 5 6 |
| . . . | 7 8 9 | 1 2 3 |       | 4 5 6 | 7 8 9 | 1 2 3 |
+=======+=======+=======+       +=======+=======+=======+
| 3 1 2 | . . . | 9 6 7 |       | 3 1 2 | 8 4 5 | 9 6 7 |
| 6 9 7 | . . . | 8 4 5 |       | 6 9 7 | 3 1 2 | 8 4 5 |
| 8 4 5 | . . . | 3 1 2 |       | 8 4 5 | 6 9 7 | 3 1 2 |
+=======+=======+=======+       +=======+=======+=======+
| 2 3 1 | 5 7 4 | . . . |       | 2 3 1 | 5 7 4 | 6 9 8 |
| 9 6 8 | 2 3 1 | . . . |       | 9 6 8 | 2 3 1 | 5 7 4 |
| 5 7 4 | 9 6 8 | . . . |       | 5 7 4 | 9 6 8 | 2 3 1 |
+=======+=======+=======+       +=======+=======+=======+

V obecné rovině jde o úlohu nesnadnou, proto vám pomůžeme několika praktickými zjednodušeními, jak po stránce omezení vstupu, tak po stránce návrhu řešení.

Omezení vstupů

Vaším úkolem bude řešit pouze sudoku, která mají právě jedno řešení. Tedy taková sudoku, kde mezi žádným řádkem a sloupcem nevzniká konflikt (jinak by řešení neexistovalo) a současně každé pole sudoku má právě jednu validní hodnotu. Ostatní sudoku si budete moci dovolit označit za neřešitelná nebo nevalidní.

Návrh řešení

Z hlediska návrhu je nejzajímavější možnost uvažovat každou buňku jako množinu možných ohodnocení této buňky. Dotyčné číslo (1, 5, …​) v ní buď je, nebo není. A protože nepotřebujeme obecné řešení pro množiny libovolných prvků, ale jen číslice 1-9, můžeme množinu reprezentovat množinou bitů. Toto uspořádání přináší v praxi výhodu - sudoku v paměti zabere méně místa, protože pro uložení stačí číselný typ s alespoň 9 bity.

Reprezentace sudoku

Sudoku tedy budeme reprezentovat 2D polem buněk - intů (resp. unsigned intů). Celé sudoku je tedy unsigned[9][9]. Díky tomu, že standard C99 garantuje, že datový typ unsigned int má alespoň 16b, můžeme spolehlivě nejnižšími 9 bity reprezentovat přítomnost čísla v buňce. Pokud je možných očíslování buňky více, bude mít buňka nastaveno více bitů na pravdivou hodnotu. Jinými slovy, na každou buňku nahlížíme jako na množinu možností. Přiřazená čísla pak jsou množinami s jediným prvkem.

// BINARY
//     8       0 // position
//     |       |
//     v       v
0000000000000001 // digit 1
0000000100000000 // digit 9, i.e. 2^8

Příjemným důsledkem takové reprezentace je, že ji můžeme využít pro popis naplnění sloupce/řádku/čtverce. Stačí nám sečíst všechny buňky v daném regionu, které mají pouze jeden prvek. Chybějící číslice díky této reprezentaci dostaneme obyčejnou negací spodních 9 bitů.

// BINARY
//     8       0 // position
//     |       |
//     v       v
0000000111111111 // all digits set (e.g. bitset of any row in solved sudoku)
0000000111111000 // digits 1, 2 and 3 not set
1111111000000111 // bit complement
0000000000000111 // lower 9 bits of the the bit complement

Políčka v sudoku budeme adresovat dvojicí indexů. Levými horním rohem jsou myšleny souřadnice (0, 0), pravým spodním rohem pak souřadnice (8, 8). Sudoku budeme do paměti ukládat po řádcích.

Struktura úlohy

Zadání budete mít za úkol vypracovat formou knihovny funkcí. Abyste si mohli svou implementaci vyzkoušet, přichystali jsme si i ukázkový main.c, který je velmi podobný mainu z referenční implementace.

Funkce, které budete implementovat, můžeme rozdělit do několika logických skupin:

  • Kontrola konfliktního ohodnocení buněk.

  • Funkce pro načtení a výpis sudoku.

  • Řešič a pomocné funkce.

V dodané kostře úlohy dále najdete i důrazně doporučené pomocné funkce, jejichž vypracování a vhodné využití vám umožní snazší řešení úlohy:

  • Funkce pro práci s buňkou - nastavení hodnoty, dotazy na hodnotu.

  • Funkce nedestruktivně zkoumající řádek, sloupec, čtverec.

Platí, že všechny své pomocné funkce píšete do svého sudoku.c. Vaše řešení se musí zkompilovat bez chyb a varování s námi dodaným sudoku.h a main.c.

Požadavky

Vaše implementace v souboru sudoku.c umožní nalezení řešení pro každé vstupní sudoku, dle specifikace z tohoto zadání.

Myslete na to, že testy budou typicky vytvářet sudoku v paměti, dle specifikace z tohoto zadání. Je tedy nutné zcela přesně dodržet formát uložení dat v paměti.

Vstup a výstup (load() a print())

Dále implementujte dvojici funkcí pro výpis a načtení sudoku. Začneme tedy požadavky na tu jednodušší.

void print(unsigned int sudoku[9][9]);

Tato funkce vytiskne sudoku na standardní výstup tak, aby odpovídalo níže uvedenému formátu mřížky.

+=======+=======+=======+
| X X X | X X X | X X X |
| X X X | X X X | X X X |
| X X X | X X X | X X X |
+=======+=======+=======+
| X X X | X X X | X X X |
| X X X | X X X | X X X |
| X X X | X X X | X X X |
+=======+=======+=======+
| X X X | X X X | X X X |
| X X X | X X X | X X X |
| X X X | X X X | X X X |
+=======+=======+=======+

Platí, že za X dosadíme:

  • Konkrétní číslici (1 až 9), pokud je dána jednoznačně.

  • '.' (tečku) pro nejednoznačnou situaci.

  • '!' (vykřičník) pokud není do daného pole možné umístit žádnou číslici.

Doporučujeme si zkontrolovat, že váš výstup dokonale odpovídá vzorové implementaci a že nemáte například zapomenutou mezeru před koncem řádku.

bool load(unsigned int sudoku[9][9]);

Tato funkce, jak už název napovídá, načítá sudoku. Musí si ale poradit se dvěma formáty vstupu. V obecné rovině platí:

  • Číslice z rozsahu 1-9 kóduje konkrétní číslo.

  • '0' kóduje neobsazenou buňku.

  • Pokud je na vstupu více různých sudoku, jsou navzájem oddělena znakem nového řádku.

  • Za posledním sudoku na vstupu se může také nacházet odřádkování.

  • Sudoku je uloženo po řádcích počínaje řádkem na indexu 0.

  • Jednotlivá sudoku budou načítána nezávisle. Můžete ale předpokládat, že pokud na vstupu máte 3 sudoku - A, B, C; pak pokud selže načítání sudoku B, nebude již načítáno sudoku C.

  • První, zjednodušený, formát obnáší jen 81 číslic, bez bílých znaků. V tomto jednoduchém formátu naleznete sudoku i na internetu a snadno ho načtete na otestování ostatních funkcí.

000456789000123456000789123312000967697000845845000312231574000968231000574968000
  • Pro druhý formát jsou na vstupu povoleny i znaky '+' , '|' , '=', '.' a ' '. Nicméně:

    • Pokud řádek začíná znakem '+', musí se za ním opakovat 3x skupina znaků =======+. Následně musí být odřádkováno.

    • Pokud řádek začíná znakem '|', musí se za ním opakovat 3x skupina tří buněk následovaných znakem '|'. Následně musí být odřádkováno.

    • Skupina tří buněk je tvořena 3 číslicemi 1-9 (k tomu 0 a '.' pro nevyřešenou buňku) a znakem ' ' před každou číslicí i za celou skupinou.

    • Jde tedy o totožný formát jako v print(), bez signalizace konfliktů.

Pokud se nepovede vstup načíst, oznamte to na jednom řádku uživateli na standardní chybový výstup a vraťte false, v opačném případě true. K vypsání na chybový výstup použijte fprintf(stderr, "…"), kde za výpustku dosadíte vaši chybovou hlášku.

Příkladem vstupu tedy může být:

000456789000123456000789123312000967697000845845000312231574000968231000574968000
+=======+=======+=======+
| . . . | 4 5 6 | 7 8 9 |
| . . . | 1 2 3 | 4 5 6 |
| . . . | 7 8 9 | 1 2 3 |
+=======+=======+=======+
| 3 1 2 | . . . | 9 6 7 |
| 6 9 7 | . . . | 8 4 5 |
| 8 4 5 | . . . | 3 1 2 |
+=======+=======+=======+
| 2 3 1 | 5 7 4 | . . . |
| 9 6 8 | 2 3 1 | . . . |
| 5 7 4 | 9 6 8 | . . . |
+=======+=======+=======+

Kontrola sudoku

bool is_valid(unsigned int sudoku[9][9]);

Je snadné se v 81 číslicích upsat, a proto se nám bude hodit funkce is_valid(). Ta zkontroluje, že mezi nastavenými číslicemi v daném řádku/sloupci/čtverci nejsou některé číslice dvakrát, nebo že v políčku může být alespoň jedna číslice. Pro validní sudoku vrací true, pro nevalidní false bez chybového hlášení. Funkce přitom uvažuje jen čísla, která jsou již dosazena.

<- freshly loaded sudoku
+=======+=======+=======+
| 4 5 6 | 7 8 9 | 1 2 4 | <-- duplicate digit 4
| . . . | . . . | . . . |
| . . . | . . . | . . . |
+=======+=======+=======+
| . 2 3 | 4 5 6 | 7 8 9 | <-- after calling solve, the first field of this row
| 1 . . | . . . | . . . |     will also make sudoku invalid.
| . . . | . . . | . . . |
+=======+=======+=======+
| . . . | . . . | . . . |
| . . . | . . . | . . . |
| . . . | . . . | . . . |
+=======+=======+=======+

Funkce kontroluje jen aktuální stav, nemusí eliminovat nebo řešit sudoku. Zejména tedy neuvažuje políčka, ve kterých je možno více hodnot.

bool needs_solving(unsigned int sudoku[9][9]);

Klíčovou otázkou při řešení sudoku je, zda vůbec potřebuje řešit. Napište tedy funkci needs_solving(), která vrátí true pokud se v sudoku nachází nějaká buňka bez přiřazené unikátní hodnoty.

Dbejte přitom na to, že tato funkce nekontroluje validitu sudoku. Jinými slovy, každá z funkcí kontroluje něco jiného. Z tohoto důvodu tedy můžete předpokládat, že výsledek volání needs_solving() je definován jen pro validní sudoku.

<- valid sudoku
+=======+=======+=======+
| 1 2 3 | 4 5 6 | 7 8 9 |
| 4 5 6 | 7 8 9 | 1 2 3 |
| 7 8 9 | 1 2 3 | 4 5 6 |
+=======+=======+=======+
| 2 3 1 | 6 7 4 | 8 9 5 |
| 5 6 4 | 9 1 8 | 2 3 7 |
| 8 9 7 | 2 3 5 | 6 1 4 |
+=======+=======+=======+
| 3 1 2 | 5 4 7 | 9 6 8 |
| 6 4 5 | 8 9 1 | 3 7 2 |
| 9 7 8 | 3 6 2 | 5 4 1 |
+=======+=======+=======+
--> false // it is already solved -> does not need solving

Řešení sudoku

bool eliminate_row(unsigned int sudoku[9][9], int row_index);
bool eliminate_col(unsigned int sudoku[9][9], int col_index);
bool eliminate_box(unsigned int sudoku[9][9], int row_index, int col_index);

Tyto tři funkce provádí vlastní eliminaci a slouží jako pomocné pro solve(). Návratovou hodnotou true pak signalizují provedení změny v sudoku. Vrácení false naopak signalizuje, že ke změně nedošlo.

Pokud byste při řešení narazili na sudoku, které obsahuje konflikt, tak ze solve() vraťte false.

<- loaded sudoku
+=======+=======+=======+
| 1 . . | . . . | . . . |
| 2 . . | . . . | . . . |
| 3 . . | . . . | . . . |
+=======+=======+=======+
| . . . | . . . | . . 4 | <-- column is missing 4, but no elimination has been done yet,
| 5 . . | . . . | . . . |     thus, the cell set holds 0x1ff (or 1_1111_1111 binary)
| 6 . . | . . . | . . . |
+=======+=======+=======+
| 7 . . | . . . | . . . |
| 8 . . | . . . | . . . |
| 9 . . | . . . | . . . |
+=======+=======+=======+
<- eliminate_* or solve() call
+=======+=======+=======+
| 1 . . | . . . | . . . |
| 2 . . | . . . | . . . |
| 3 . . | . . . | . . . |
+=======+=======+=======+
| ! . . | . . . | . . 4 | <-- column is missing 4, but the row already contains
| 5 . . | . . . | . . . |     4 - thus, solve() resulted in cell holding 0x0,
| 6 . . | . . . | . . . |     and thus, the cell is unassignable
+=======+=======+=======+
| 7 . . | . . . | . . . |
| 8 . . | . . . | . . . |
| 9 . . | . . . | . . . |
+=======+=======+=======+

Mohlo by se stát (a stává se to často) že eliminací nedostanete žádnou novou informaci. Uvažte například, co se stane, pokud odstraníme všechny číslice 6 a 9. Při první eliminaci se zjistí, že políčka musí obsahovat buď 6 nebo 9, ale nebudeme to schopni jednoznačně rozhodnout.

Zjevně takto nejsme schopni vyřešit všechna (dokonce ani jednoznačná) sudoku. Jedná se však o užitečný výsledek, kterým by se případné univerzální řešení dalo najít podstatně rychleji (například v prvním bonusovém úkolu), nebo by ho daná aplikace mohla použít jako nápovědu pro lidského řešitele.

bool solve(unsigned int sudoku[9][9]);

Funkce solve() dostane na vstupu 2D pole sudoku ve výše popsaném formátu a bude podle pravidel eliminovat možnosti nenastavených číslic, dokud nebude sudoku vyřešené nebo se po eliminaci nic nezmění. Pokud se sudoku podařilo kompletně vyřešit, vrátí solve() hodnotu true. V opačném případě pak false, a to bez ohledu na to, zda byla nějaká čísla dosazena či nikoliv.

Eliminační pravidla (ilustrováno na řádku, vztahuje se nicméně i na sloupce a čtverce 3×3):

  • Z možností pro nedosazené buňky v řádku odebereme všechna v řádku již dosazená čísla.

  • Eliminaci provádíme v pořadí:

    • řádky

    • sloupce

    • čtverce, z levého horního rohu do pravého spodního rohu po řádcích.

Řešení sudoku hledejte iterativně! To jest, opakovaně všechny řádky, poté všechny sloupce a poté všechny čtverce 3×3 a znovu. Eliminujte možné číslice bez využití rekurze.

Uvědomte si, že eliminaci nemusíte provádět formou každý-s-každým.

+=======+=======+=======+
| . . . | 4 5 6 | 7 8 9 |
| . . . | 1 2 3 | 4 5 6 |
| . . . | 7 8 9 | 1 2 3 |
+=======+=======+=======+
| 3 1 2 | . . . | 9 6 7 |
| 6 9 7 | . . . | 8 4 5 |
| 8 4 5 | . . . | 3 1 2 |
+=======+=======+=======+
| 2 3 1 | 5 7 4 | . . . |
| 9 6 8 | 2 3 1 | . . . |
| 5 7 4 | 9 6 8 | . . . |
+=======+=======+=======+

Uvažme první prázdný čtverec 3x3 v sudoku výše. Když si rozepíšeme požadavky sloupců a řádků, dostaneme:

DIGITS:     1 2 3 4 5 6 7 8 9
1st row:    1 2 3
2nd row:                7 8 9
3rd row:          4 5 6
1st column: 1     4     7
2nd column:   2     5     8
3rd column:     3     6     9

Což přímo zadává pozici všech devíti číslic. Sudoku výše tak vyřešíme hned s první vlnou eliminací po řádcích a sloupcích.

Bonusové rozšíření

Pokud sami rádi řešíte sudoku, máme pro vás ještě dva nepovinné bonusy.

Backtracking [10 bodů]

Využijte backtracking k řešení libovolného sudoku:

  • Pokud nelze eliminací zjistit číslici, jednu zvolte.

  • Pro upravené sudoku zkuste najít řešení.

  • V případě, že provedená úprava k výsledku nevede, zkuste dosadit jinou z možností.

  • Pro popis návratové hodnoty viz solve().

bool generic_solve(unsigned int sudoku[9][9]);

Testy bonus detekují automaticky. Bonus v řešení povolíte tak, že v hlavičkovém souboru sudoku.h odkomentujete níže uvedený řádek.

//#define BONUS_GENERIC_SOLVE
$ cat 81x_echo_0 \
    | /home/kontr/pb071/hw02/sudoku --raw --print --generic-solve --print
LOAD

RAW:
000000000000000000000000000000000000000000000000000000000000000000000000000000000

GENERIC_SOLVE
SOLVED

PRINT:
+=======+=======+=======+
| 1 2 3 | 4 5 6 | 7 8 9 |
| 4 5 6 | 7 8 9 | 1 2 3 |
| 7 8 9 | 1 2 3 | 4 5 6 |
+=======+=======+=======+
| 2 3 1 | 6 7 4 | 8 9 5 |
| 5 6 4 | 9 1 8 | 2 3 7 |
| 8 9 7 | 2 3 5 | 6 1 4 |
+=======+=======+=======+
| 3 1 2 | 5 4 7 | 9 6 8 |
| 6 4 5 | 8 9 1 | 3 7 2 |
| 9 7 8 | 3 6 2 | 5 4 1 |
+=======+=======+=======+

Uvědomte si, že i prostý příklad s vynecháním všech 4, 5 a 8, 9 vede k nutnosti uvážit více úrovní backtrackingu.

Při vhodně nastaveném backtrackingu vám může užití rekurze výrazně zjednodušit kód, nevhodné užití ale k výsledku spíše nepovede.

Generování sudoku [5 bodů]

Napište funkci, která ze sudoku opakovaně zkusí odebrat náhodně číslice tak dlouho, dokud je sudoku řešitelné eliminací (solve(), nikoliv druhý bonus). Výsledkem budiž sudoku, ve kterém odebrání kterékoliv další číslice povede ke ztrátě možnosti získat řešení eliminací.

Tento bonus v mainu povolíte odkomentováním odpovídajícího #define v sudoku.h.

void generate(unsigned int sudoku[9][9]);

Například ze sudoku na začátku bychom mohli odebrat ještě pár čísel:

# echo 000456789000123456000789123312000967697000845845000312231574000968231000574968000 \
    | /home/kontr/pb071/hw02/sudoku --seed 9 --generate --print  --raw
LOAD

GENERATE

PRINT:
+=======+=======+=======+
| . . . | . . 6 | . 8 . |
| . . . | 1 2 . | . . 6 |
| . . . | 7 8 . | 1 . 3 |
+=======+=======+=======+
| . 1 . | . . . | 9 . 7 |
| 6 . . | . . . | 8 4 . |
| . . 5 | . . . | 3 . . |
+=======+=======+=======+
| 2 . . | . . 4 | . . . |
| . . . | . 3 1 | . . . |
| 5 7 4 | 9 . . | . . . |
+=======+=======+=======+

RAW:
000006080000120006000780103010000907600000840005000300200004000000031000574900000

Můžete si vyzkoušet, že toto sudoku je ještě řešitelné se solve(), ale po odebrání další číslice bychom museli hádat a zkoušet možnosti. Podobně další aplikací generate() na výsledné sudoku bychom dostali již nezměněné sudoku.

Na nevalidním vstupním sudoku nic nevypisujte, stačí ve funkci solve() nepokračovat.

Navíc funkce generate() vytvoří takové minimální sudoku, které zůstane minimální i po zobrazení uživateli.

Pro jednoduchost bude vyžadováno, aby sudoku upravené funkcí generate() neobsahovalo žádnou buňku, která by měla částečně předvyplněná nevalidní dosazení (Kromě jednoznačného dosazení.). Takové nejednoznačné buňky musí být přepsány na buňky, které nevylučují dosazení libovolné číslice (1-9).

Poznámky

  • Vyjma testů bonusu je vždy užito referenční sudoku.h.

  • Dejte si pozor, kdo vlastní paměť, ve které je sudoku uloženo. Zejména dbejte toho, že v C se mezi funkcemi pole nepředávají kopiemi.

  • Funkce load() má v proměnné sudoku paměť, o jejímž počátečním obsahu nesmíte nic předpokládat.

  • Můžete - a je to zcela správně - mít potřebu vyzkoušet referenční implementaci na vlastních sudoku. Referenční implementace nicméně předpokládá unixové ukončení řádků vstupního souboru. S převodem textového souboru ve formátu CRLF do formátu LF vám pomůže příkaz dos2unix.

Vzorová implementace

Vzorovou implementaci naleznete na Aise umístěnou v /home/kontr/pb071/hw02/sudoku. Součástí kostry zadání je i main použitý ve vzorové implementaci, jehož dokončením můžete začít práci na úloze.

Které přepínače zkouší kterou funkci zjistíte pomocí příkazu /home/kontr/pb071/hw02/sudoku --help.

Běžně se standardní a chybový výstup vypisuje přímo do terminálu dohromady. Pokud budete chtít výstup rozdělit, můžete si oba výstupy přesměrovat do dvou souborů, např. takto:

echo [INPUT] | /home/kontr/pb071/hw02/sudoku [ARGUMETNS] > output.stdout 2> output.stderr

Opravy v zadání

Fix undefined behavior of bonus generate() function in assignment

a76652f 2024-03-23 21:48 atom194
 -562,2 +562,13  funkci `solve()` nepokračovat.
 
+[WARNING]
+====
+Navíc funkce `generate()` vytvoří takové minimální sudoku, které zůstane
+minimální i po zobrazení uživateli.
+
+Pro jednoduchost bude vyžadováno, aby sudoku upravené funkcí `generate()`
+neobsahovalo žádnou buňku, která by měla částečně předvyplněná nevalidní
+dosazení (Kromě jednoznačného dosazení.). Takové nejednoznačné buňky musí být
+přepsány na buňky, které nevylučují dosazení libovolné číslice (1-9).
+====
+
 == Poznámky

Add time to deadline of HW02

821b9e9 2024-03-20 09:08 Jozef Sabo
 -6,4 +6,4  solution-path: /home/kontr/pb071/hw02/sudoku
 publish: now
-deadline-early: 2024-03-20
-deadline-final: 2024-03-27
+deadline-early: 2024-03-20 24:00
+deadline-final: 2024-03-27 24:00
 authors:

Remove gramatical error in comment for is_valid example

1a1b0dc 2024-03-17 00:33 Ota Mikušek
 -272,4 +272,4  Funkce přitom uvažuje jen čísla, která jsou již dosazena.
 +=======+=======+=======+
-| . 2 3 | 4 5 6 | 7 8 9 | <-- after calling solve, first field of this row will
-| 1 . . | . . . | . . . |     also make sudoku invalid.
+| . 2 3 | 4 5 6 | 7 8 9 | <-- after calling solve, the first field of this row
+| 1 . . | . . . | . . . |     will also make sudoku invalid.
 | . . . | . . . | . . . |

Make example for is_valid function more understandable

048da09 2024-03-15 17:23 Ota Mikušek
 -272,4 +272,4  Funkce přitom uvažuje jen čísla, která jsou již dosazena.
 +=======+=======+=======+
-| . 2 3 | 4 5 6 | 7 8 9 | <-- it is not possible to fill the first
-| 1 . . | . . . | . . . |     field of this row with any number
+| . 2 3 | 4 5 6 | 7 8 9 | <-- after calling solve, first field of this row will
+| 1 . . | . . . | . . . |     also make sudoku invalid.
 | . . . | . . . | . . . |