Att lösa Sudoku. Metoden Solve (börjar nedtill på sid 3)

Relevanta dokument
Tentamen TEN1 HI

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Föreläsning 8 Innehåll

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Grundläggande programmering med C# 7,5 högskolepoäng

DD1361 Programmeringsparadigm. Carina Edlund

Bankkonto - övning. Övning 2 Skriv en metod, geträntan, som returnerar räntan.

Dagens program. Programmeringsteknik och Matlab. Objektorienterad programmering. Vad är vitsen med att ha både metoder och data i objekten?

Lösningsförslag tentamen FYTA11 Java

Tentamen TEN1 HI

JavaScript del 3 If, Operatorer och Confirm

Laboration 3, uppgift En klass för en räknare

Föreläsning 6. Rekursion och backtracking

Introduktion till programmering D0009E. Föreläsning 5: Fruktbara funktioner

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Tentamen för kursen Objektorienterad programvaruutveckling GU (DIT010)

Tentamen i Introduktion till programmering

TUTORIAL: KLASSER & OBJEKT

Tentamen DE12, IMIT12, SYST12, ITEK11 (även öppen för övriga)

Introduktion till programmering SMD180. Föreläsning 5: Fruktbara funktioner

TENTAMEN I PROGRAMMERING. På tentamen ges graderade betyg:. 3:a 24 poäng, 4:a 36 poäng och 5:a 48 poäng

public static void mystery(int n) { if (n > 0){ mystery(n-1); System.out.print(n * 4); mystery(n-1); } }

Föreläsning 6. Rekursion och backtracking

Programmering I Tobias Wrigstad fredag, 2009 augusti 28

Påminnelse: en datatyp för bilder. Programmering. En datatyp för bilder. Spegelbild. hh.se/db2004

Tentamen i Grundläggande programmering STS, åk 1 lördag

tentaplugg.nu av studenter för studenter

Programkonstruktion och Datastrukturer

Objektorienterad Programkonstruktion. Föreläsning jan 2016

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

Programmering för språkteknologer II, HT2014. Rum

Objektorienterad programmering D2

Kryptokorsordslösare Programmeringsmetodik DV (period 2) Inlämningsuppgift 1

Tentamen EDAF30 Programmering i C++

Lambdas. (och fler design patterns) Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2017

Föreläsning 11: Rekursion

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

Objektsamlingar i Java

Metodanrop - primitiva typer. Föreläsning 4. Metodanrop - referenstyper. Metodanrop - primitiva typer

Användarhandledning Version 1.2

if (n==null) { return null; } else { return new Node(n.data, copy(n.next));

Tentamen, EDA501/EDAA20 Programmering M MD W BK L

Föreläsning 5. Rekursion

F11 - Rekursion. ID1004 Objektorienterad programmering Fredrik Kilander

Föreläsning 7 Innehåll. Rekursion. Rekursiv problemlösning. Rekursiv problemlösning Mönster för rekursiv algoritm. Rekursion. Rekursivt tänkande:

Exempel: Exempel: Exempel: Exempel: $djur=array("ko","katt","älg"); foreach ($djur as $d) { echo $d. " "; } Resultat. ko katt älg

Objektorientering i liten skala

Vem är vem på kursen. Objektorienterad programvaruutveckling GU (DIT011) Kursbok Cay Horstmann: Big Java 3rd edition.

Rekursion. Att tänka rekursivt Att programmera rekursivt i Java Exempel. Programmeringsmetodik -Java 254

Rekursion och induktion för algoritmkonstruktion

kl Tentaupplägg

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

TDIU01 Programmering i C++

Karlstads Universitet, Datavetenskap 1

Algoritmer och datastrukturer H I HÅKAN S T R Ö M B E R G N I C K L A S B R A N D E F E L T

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Backcode. Jonathan Crusoe TDP019 Projekt: Datorspråk Linköpings universitet

TENTAMEN: Objektorienterad programmering. Läs detta! Skriv din tentamenskod på varje blad (så att vi inte slarvar bort dem).

Del A (obligatorisk för alla)

Introduktion till algoritmer - Lektion 4 Matematikgymnasiet, Läsåret Lektion 4

Objektorienterad programmering i Java I

SP:PROG3 HT12 Tenta

Textsträngar från/till skärm eller fil

TENTAMEN OOP

OOP Objekt-orienterad programmering

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Exempelduggan. Luffarschack. Koda spel

Algoritmer, datastrukturer och komplexitet

Tentamen i Grundläggande Programvaruutveckling, TDA548

Lösningar till tentamen i EIT070 Datorteknik

Inledande programmering med C# (1DV402) Tärningarna ska kastas

BEGREPP HITTILLS FÖRELÄSNING 2 SAMMANSATTA UTTRYCK - SCHEME DATORSPRÅK

Föreläsning 5 Mer om funktioner, villkor

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

Instruktioner - Datortentamen TDDD73 Funktionell och imperativ programmering i Python TDDE24 Funktionell och imperativ programmering del 2

Omtentamen för TDA540 Objektorienterad Programmering. Institutionen för Datavetenskap CTH HT-16, TDA540. Dag: , Tid:

Uppgift 1 ( Betyg 3 uppgift )

OOP Objekt-orienterad programmering

Här ska jag presentera en variant

Gissa det hemliga talet

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

FÖRELÄSNING 2, TDDC74, VT2018 BEGREPP PROBLEMLÖSNING MED HJÄLP AV FALLANALYS PROBLEMLÖSNING MED HJÄLP AV REKURSION

Programmeringsteknik I

Föreläsning 5-6 Innehåll

Tentamen i Objektorienterad programmering

Tentamen OOP

Tentamen, EDAA10 Programmering i Java

F5 Selektion och iteration. ID1004 Objektorienterad programmering Fredrik Kilander

HI1024 Programmering, grundkurs TEN

Föreläsning 3-4 Innehåll

LÖSNINGSFÖRSLAG TENTAMEN

Föreläsning 8: Exempel och problemlösning

Uppgift 1 (grundläggande konstruktioner)

Tentamen, Algoritmer och datastrukturer

Föreläsning 5 Innehåll. Val av algoritm och datastruktur. Analys av algoritmer. Tidsåtgång och problemets storlek

Tentamen i Objektorienterad programmering

Introduktion. Klasser. TDP004 Objektorienterad Programmering Fö 2 Objektorientering grunder

JAVA Mer om klasser och objektorientering

(Man brukar säga att) Java är... Denna föreläsning. Kompilering av Java. Historik: Java. enkelt. baserat på C/C++ Allmänt om Java

Föreläsning 5 Innehåll

MATLAB. Python. Det finns flera andra program som liknar MATLAB. Sage, Octave, Maple och...

Transkript:

Att lösa Sudoku Problem av matematisk natur kan ofta snabbt lösas med dator och lite programmering. Därför har sällan matematiska pussel som Sudoku lockat att lösa för hand, men jag har länge tänkt att det vore väl enkelt att lösa med några rader kod som tar fram lösningen. Sagt och gjort, men matematiskt är det också intressant när brute force -algoritmer där helt enkelt alla möjligheter provas ger så många kombinationer att det även för en snabb dator med sina miljarder klockcykler per sekund tar oerhört lång tid att prova sig fram grunden för säker kryptering är just att det finns så oerhört många kombinationer att prova att inte ens en dator inom rimlig tid kan knäcka koden. För Sudoku handlar det i standardfallet om 9 x 9 rutor med 9 möjliga värden, vilket kan uppfattas som nära ett tal med 81 siffror (förutom att siffran 0 inte får användas). Rent matematiskt motsvarar det 9^81 = 1,97 x 10^77, alltså ett tal med 78 siffror. Med tanke på att en miljard skrivs 1 x 10^9, en miljard miljarder 1 x 10^18 så är det oerhört många kombinationer att prova, även för en dator. Dock finns många enkla regler att följa i Sudoku som minskar ner mängden alternativ att ens överväga. Att skriva 1:or på alla lediga platser är onödigt att ens överväga att ens skriva två 1:or på samma rad ger ingen lösning. Så även brute force -algoritmen kan kapa bort många kombinationer tidigt genom att utvärdera om ett alternativ kan leda till en lösning, eller ej. Ett enkelt försök att lösa Sudoku utformas med: + en matris med 9 x 9 värden, 0 för ledigt och 1-9 för valda värden att testa + en funktion som börjar testa på första rutan med en 1:a och verifiera att detta är ett giltigt värde + är det giltigt anropar funktionen sig själv (rekursion) för att testa nästa ruta + vid retur från det rekursiva anropet testas en 2:a och så vidare + till slut har man nått den sista rutan och bör ha en lösning Från start har matrisen några värden ifyllda, enligt det givna Sudoku-problemets siffror färre ifyllda på svårare Sudoku, fler på de lite enklare, men funktionen hanterar en redan ifylld ruta genom att inte prova 1 till 9 utan bara kolla att det fortfarande är en giltig lösning. För att enkelt koda detta väljs script-språket php (finns förinstallerat för kommandorad på Mac, enkelt att installera i Windows och utgör grunden för mycket av webbens verktyg som WordPress, Joomla, Drupal med flera). Koden utformas som en klass (objektorienterad programmering) med data för själva Sudoku-matrisen samt metoder för att kolla giltighet för en ruta och den rekursiva metoden för att lösa ett steg i taget. Index i matrisen startar på 0 så rad och kolumn-värden går från 0 till 8 (mot de för människor mer naturliga 1 till 9). Metoden Solve (börjar nedtill på sid 3) Koden inleder med att räkna fram nästa ruta genom att öka kolumnvärdet med 1 och går man över kanten nollställs kolumnen och raden ökas med ett från ($r,$c) till ($rn,$cn). Därefter kontrolleras om det redan finns ett värde på den givna platsen, isåfall kontrolleras giltigheten med metoden Verify och är det giltigt sker rekursivt anrop (metoden anropar sig själv) med argument för nästa ruta. Finns inget värde (tom ruta är 0) så startas en loop för att prova varje möjlig siffra på den givna platsen och är siffran giltig enligt metoden Verify sker rekursivt anrop för nästa ruta. Sid 1 av 5

Metoden Verify (mitt på sid 3) Som argument tar funktionen rad och kolumn att kontrollera giltighet för siffra i matrisen. Enligt reglerna för Sudoku får inte samma siffra finnas på samma rad eller i samma kolumn, ej heller i den mindre 3x3-matris siffran finns inom. Med en loop från 1 till 9 kontrolleras att inte samma siffra som på given plats finns på raden, därefter i kolumnen. För den mindre 3x3-rutan beräknas först rutans startrad och startkolumn (0, 3 eller 6), därefter kontrolleras genom nästade loopar att inte samma siffra som på given plats finns på annan plats inom kvadraten. Metoden returnerar falskt så fort samma siffra hittas och klaras alla tester returneras till slut sant. Övrig kod i klassen En konstruktor som initierar matrisen till 0-värden samt en metod Show som visar matrisen på ett enkelt sätt är också implementerade. En räknare count håller reda på hur många anrop som gjorts till metoden Verify. Huvudprogrammet (nedre halvan av sid 4) Här skapas ett objekt av klassen SudokuSolver, så sätts startvärden genom att tilldela 9 rader med 9 siffervärden (matas in från exempelvis tidning), så anropas metoden Solve för första rutan (0,0). Utfall (utmatning upptill sid 5) Hur fungerar koden att köra då? Jotack, över förväntan, det svåra problemet ur SvD 2016-03-19 där 26 siffror av 81 siffror givits tar ca 1 sekund att lösa och istället för de många miljarder alternativen så har metoden Verify kontrollerat 156 380 möjliga siffror och den enda giltiga lösningen hittades efter 116 070 kontroller. Den enklare rutan med 36 givna siffror löstes på blott 0,1 sekund. Utökning I standardform gäller storleken 9x9 med siffror 1-9 i varje ruta, men större Sudoku kan tänkas med i grunden samma regler. 16x16 och 25x25 exempelvis, varvid siffrorna 1-9 utökas till 1-16 resp. 1-25. För att slippa 2-ställiga tal kan bokstäver a-g resp. a-p användas vid presentation av matrisen. Koden justeras så förekomsten av siffran 3 ersätts med SIZE och 9 med SQUARE därefter kan SIZE definieras till 4 eller 5 så fungerar exakt samma kod även för större problem. Hade koden varit långsam kunde man portera till ett kompilerande språk som C/C++ och använda flera processorkärnor genom att dela problemet i flera delar, men det behövdes nu inte i detta fall. Koden 141 rader PHP-kod (varav 32 rader enbart kommentar och 18 tomrader så under 100 rader kod): <?php // Definiera storlek på lilla rutan, 3, 4, 5 eller... define('size', 3); define('square', SIZE*SIZE); // Klass med data och metoder class SudokuSolver { public $m=array(); // Matrisen med siffror, 2-dimensionell vektor public $count=0; // Antal kontroller i Verify Sid 2 av 5

// Konstruktor som initierar data - fyll matrisen med nollor function construct() { for($r=0; $r<square; $r++) for($c=0; $c<square; $c++) $this->m[$r][$c]=0; // Visa matrisen function Show() { // Symboler - siffror 0-9, sen bokstäver static $sym="0123456789abcdefghijklmnopqrstuvwxyz"; // 0 1 2 3 4 $set=0; // Räkna antal satta värden for($r=0; $r<square; $r++) { for($c=0; $c<square; $c++) { echo ($c>0 && $c%size==0? " " : "").$sym[$this->m[$r][$c]]; if($this->m[$r][$c]>0) $set++; echo " ".(($r+1)%size==0? "\n\n" : "\n"); // Ange andel löst om ej helt klar if($set<square*square) printf("stat: %d (%1.1f%%) done\n", $set, (100.0*$set)/(SQUARE*SQUARE)); // Kolla giltighet för siffra i matrisen på rad $r, kolumn $c function Verify($r, $c) { $this->count++; // Räkna upp antal gjorda kontroller // Observera: // + vid kontroll av dublettvärden i rad, kolumn och lilla rutan // jämförs med alla andra värden - utom på den egna platsen $r,$c // + värdet 0 för annan ledig ruta ger ingen dubblett då detta värde // ej valts ännu, men kommer att väljas och kontrolleras då // Kolla efter dublett i samma rad for($i=0; $i<square; $i++) if($i!=$c && $this->m[$r][$i]==$this->m[$r][$c]) return false; // Ogiltig då samma siffra redan fanns på raden // Kolla efter dublett i samma kolumn for($i=0; $i<square; $i++) if($i!=$r && $this->m[$i][$c]==$this->m[$r][$c]) return false; // Ogiltig då samma siffra redan fanns i kolumnen // Kolla efter dublett i samma lilla ruta $rstart=size*intval($r/size); // Rutans startrad $cstart=size*intval($c/size); // Rutans startkolumn // Loopa över lilla rutans rader och kolumner for($radd=0; $radd<size; $radd++) for($cadd=0; $cadd<size; $cadd++) if($rstart+$radd!=$r && $cstart+$cadd!=$c && $this->m[$rstart+$radd][$cstart+$cadd]==$this->m[$r][$c]) return false; // Ogiltig då samma siffra redan fanns i lilla rutan // Returnera sant då ingen dublett fanns (ännu) return true; // Lös Sudoku från rad $r, kolumn $c och framåt function Solve($r, $c) { // Lite återkoppling på antal försök och var vi är if($this->count%10000==0) Sid 3 av 5

echo "\r".$this->count." at ($r,$c)"; // Koll om färdig - då vi hamnat på raden under matrisen if($r>=square) { // Visa lösning echo "\rsolved after ".$this->count." checks:\n"; $this->show(); else { // Beräkna nästa ruta $rn=$r; $cn=$c+1; if($cn>=square) { $cn=0; $rn++; // Kolla vad vi har på rad $r, kolumn $c if($this->m[$r][$c]>0) { // En redan given siffra, kolla om giltig och fortsätt isåfall if($this->verify($r, $c)) $this->solve($rn, $cn); // Fortsätt (rekursion) else { // En ledig ruta, prova varje möjlig siffra och fortsätt om giltig for($digit=1; $digit<=square; $digit++) { $this->m[$r][$c]=$digit; if($this->verify($r, $c)) $this->solve($rn, $cn); $this->m[$r][$c]=0; // Sätt siffra // Fortsätt (rekursion) // Återställ till ledig ruta //--- Huvudprogram ---------------------------------------- // Skapa instans av klassen $sudo=new SudokuSolver(); // SvD 2016-03-19, svår $sudo->m[0]=array(0, 0, 0, 0, 0, 0, 0, 0, 0); $sudo->m[1]=array(6, 4, 0, 0, 1, 7, 0, 0, 0); $sudo->m[1]=array(6, 4, 0, 0, 0, 0, 0, 0, 0); $sudo->m[2]=array(0, 0, 0, 0, 2, 0, 3, 6, 1); $sudo->m[3]=array(5, 0, 6, 0, 4, 0, 0, 0, 0); $sudo->m[4]=array(0, 0, 0, 0, 0, 6, 4, 2, 0); $sudo->m[5]=array(0, 0, 0, 2, 8, 0, 0, 0, 9); $sudo->m[6]=array(0, 0, 0, 5, 0, 0, 7, 4, 0); $sudo->m[7]=array(4, 0, 5, 0, 6, 9, 2, 0, 0); $sudo->m[8]=array(8, 0, 0, 0, 0, 0, 0, 0, 0); // Visa startläge echo "Starting with:\n"; $sudo->show(); // Lös från rad 0, kolumn 0 echo "Solving...\n"; $start=microtime(true); $sudo->solve(0, 0); $stop=microtime(true); // Rapportera körtid och totalt antal kontroller printf("\rdone after ".$sudo->count." checks in %0.3fs.\n", $stop-$start);?> Sid 4 av 5

Utmatning Vid körning visas: Starting with: 000 000 000 640 017 000 000 020 361 506 040 000 000 006 420 000 280 009 000 500 740 405 069 200 800 000 000 Stat: 26 (32.1%) done Solving... Solved after 116070 checks: 312 695 874 648 317 592 957 824 361 526 943 187 189 756 423 734 281 659 291 538 746 475 169 238 863 472 915 Done after 156380 checks in 0.985s. Värt att notera är att nya PHP version 7 är ca 2.25 ggr snabbare än PHP version 5 för denna kod. Slutord Med ovanstående exempel vill vi visa på möjligheten att lösa beräkningsproblem med en systematisk metod. Brute force där alla möjliga kombinationer provas leder ofta till en kombinatorisk explosion med så många alternativ att inte ens en dator når ett resultat inom rimlig tid, men går det att tidigt eliminera alternativ så kan lösningen hittas inom rimlig tid - och i fallet Sudoku hittas lösningen på det svåra problemet på under sekunden! Hör gärna av Dig om Du behöver hjälp att lösa något beräkningsproblem i Din verksamhet. Fotnot: Koden togs fram på en Apple MacBook, en liten smidig men inte särskilt snabb dator, under lite väntetid på en flygplats. Sid 5 av 5