Sudoku Solver - Verständnisfrage

  • C

Es gibt 33 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Sudoku Solver - Verständnisfrage

    Hey Leute,

    ich hatte mir in den Kopf gesetzt einen Sudoku Solver zu programmieren. Ich habe bewusst NICHT im Internet danach gesucht und mir meine eignen Gedanken gemacht.
    Das Endprodukt war ein Solver, welcher an der Stelle aufgehört hat zu lösen, wo ein Widerspruch herauskam. Habe alles probiert, bin aber nicht weiter gekommen.
    Dann habe ich mich mal im Internet inspirieren lassen und damit letztendlich auch meinen Code so anpassen können, dass er funktioniert. Jedoch verstehe ich die Funktionsweise von
    solveField() nicht 100%. Wenn ich es mir Schrittweise debugge, dann stelle ich fest, dass der x Wert immer sobald es einen Widerspruch gibt in das zuletzt genutzte Feld springt.
    Dies ist mir aber aus dem Code nicht ersichtlich. Für mich wird x immer weiter erhöht und wenn die Zeile zu Ende ist, geht es in die Nächste. Das Zurückspringen ist echt irgendwie komisch.

    Ich hoffe mir kann jmd da auf die Sprünge helfen.

    Hier der Code:


    Spoiler anzeigen

    C-Quellcode

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <locale.h>
    4. #define G 9
    5. int feld[G][G] = {
    6. {0,4,5,7,0,0,0,6,1}, //Reihe 1
    7. {0,0,0,8,0,5,4,0,0}, //Reihe 2
    8. {0,0,7,6,4,0,0,0,2}, //Reihe 3
    9. {5,0,0,9,6,4,0,0,0}, //Reihe 4
    10. {6,0,8,5,0,1,0,3,0}, //Reihe 5
    11. {0,7,0,0,0,0,0,9,0}, //Reihe 6
    12. {3,0,2,4,0,0,1,0,0}, //Reihe 7
    13. {9,0,0,1,0,0,0,0,6}, //Reihe 8
    14. {0,0,0,0,0,3,8,0,0}};//Reihe 9
    15. void printField();
    16. void fillField();
    17. int solveField(int x, int y);
    18. int checkSudoku(int zahl, int x, int y);
    19. int getNextEmptyField(int *a, int *b);
    20. int main(void)
    21. {
    22. setlocale( LC_ALL, "" );
    23. //Programmablauf
    24. printField();
    25. printf("Taste drücken zum Starten...");
    26. getch();
    27. //Lösen
    28. solveField(0,0);
    29. printf("Sudoko gelöst!");
    30. getch();
    31. return 0;
    32. }
    33. //Sukoku Lösen
    34. int solveField(int x, int y)
    35. {
    36. if(x == G) //Zeilenende
    37. {
    38. y++;
    39. x = 0;
    40. }
    41. if(y == G)
    42. {
    43. //-> Solved
    44. return 1;
    45. }
    46. //Prüfen ob Feld gefüllt
    47. if(feld[y][x] > 0)
    48. {
    49. //Nächstes Feld
    50. return solveField(x+1, y);
    51. }
    52. //Zahl von 1-9 in aktuelles freies Feld einsetzen
    53. int z;
    54. for(z = 1; z < 10; z++)
    55. {
    56. //Zeilen prüfen - Spalten prüfen - 3er Päckchen prüfen
    57. if(checkSudoku(z,x,y) == 0)
    58. {
    59. feld[y][x] = z;
    60. //Auskommentieren zum Schrittweise anzeigen
    61. //printf("X: %d | Y: %d",x+1,y+1);
    62. //getch();
    63. //printField();
    64. if(solveField(x, y) == 1) //Gerade diese Zeile ist mir nicht ganz klar...
    65. {
    66. //Fertig -> Print
    67. printField();
    68. }
    69. } //else nächste Zahl z
    70. }
    71. feld[y][x] = 0; //Auch wenn die Zeile auf 0 gesetzt wird, dann ist doch x um +1 erhöht worden und es wäre jetzt ein Feld leer und übersprungen?
    72. return 0;
    73. }
    74. //Spielfeld nach den Regeln überprüfen
    75. int checkSudoku(int zahl, int x, int y)
    76. {
    77. int i,j;
    78. //Zeilen
    79. for(i = 0; i < 9; i++)
    80. {
    81. if(feld[y][i] == zahl)
    82. {
    83. return 1; //Vorhanden
    84. }
    85. }
    86. //Spalten
    87. for(i = 0; i < 9; i++)
    88. {
    89. if(feld[i][x] == zahl)
    90. {
    91. return 1; //Vorhanden
    92. }
    93. }
    94. //3er Kästchen
    95. int x_box, y_box;
    96. // Passende Ecke der Box herausfinden
    97. x_box = (int)(x / 3) * 3;
    98. y_box = (int)(y / 3) * 3;
    99. for(i = y_box; i < y_box + 3; i++)
    100. {
    101. for(j = x_box; j < x_box + 3; j++)
    102. {
    103. if(feld[i][j] == zahl)
    104. return 1; //Vorhanden
    105. }
    106. }
    107. return 0; //Nicht vorhanden.
    108. }
    109. //Funktion für späteres Vorhaben (zuerst auch für den algo. genutzt)
    110. int getNextEmptyField(int *a, int *b)
    111. {
    112. int i,j;
    113. for(i = 0; i < 9; i++)
    114. {
    115. for(j = 0; j < 9; j++)
    116. {
    117. if(feld[i][j] < 1)
    118. {
    119. //Treffer
    120. *a = i;
    121. *b = j;
    122. return 1;
    123. }
    124. }
    125. }
    126. //Kein Feld gefunden
    127. return 0;
    128. }
    129. //Funktion für späteres Vorhaben
    130. void fillField()
    131. {
    132. int x,y,w = 0;
    133. printf("Zeile: ");
    134. scanf("%d", &x);
    135. printf("Spalte: ");
    136. scanf("%d", &y);
    137. //Feld auf leere Stelle prüfen
    138. if(feld[y-1][x-1] < 1)
    139. { //Leer
    140. printf("Zahl: ");
    141. scanf("%d", &w);
    142. //Überprüfung einbauen ob Zahl korrekt
    143. feld[y-1][x-1] = w;
    144. }
    145. main();
    146. }
    147. //Ausgabe des Spielfeldes in der CMD
    148. void printField()
    149. {
    150. //Console leeren
    151. system("cls");
    152. system("color 0F");
    153. printf(" .:[Sudoku Solver v1]:.\n\n");
    154. int i,j;
    155. //Zeilen, Y-Achse
    156. printf(" 1 2 3 4 5 6 7 8 9\n");
    157. for(i=0; i<9; i++)
    158. {
    159. printf(" +---+---+---+---+---+---+---+---+---+\n");
    160. printf("%d |", i+1);
    161. //Spalten, X-Achse
    162. for(j=0; j<9; j++)
    163. {
    164. if(feld[i][j] < 1)
    165. { //Feld leer
    166. printf(" |");
    167. }
    168. else
    169. { //Feld gefüllt
    170. printf(" %d |", feld[i][j]);
    171. }
    172. }
    173. printf("\n");
    174. }
    175. printf(" +---+---+---+---+---+---+---+---+---+\n");
    176. }




    LG

    Michael

    MB-Tech schrieb:

    auf die Sprünge helfen.
    Wenn das Verhalten reproduzierbar merkwürdig ist, setz einen Haltepunkt rein und vergleiche das was er macht mit dem was er tun soll.
    Da solltest Du den Fehler eigentlich in 3 Minuten gefunden haben.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Der Trick ist, dass die Funktion rekursiv aufgerufen wird und die Variablen x und y auf dem Stack liegen. So kann man jederzeit zum alten Zustand zurück, wenn checkSudoku 1 zurückliefert und eine neue Zahl probieren.
    Bevor ich das jetzt versuche genau zu erklären, such mal im Netz etwas nach Backtracking.
    Das Backtracking habe ich mir durchgelesen aber nicht zu 100% verstanden. Wie genau kann man "zurückspringen"? Zudem verwirrt das Return 0 noch ein wenig.

    Gruß

    MB-Tech schrieb:

    Wie genau kann man "zurückspringen"?
    Du musst Dir beim Durchführen eines Schrittes den aktuellen Zustand merken. Wenn Du die 1 testest, weißt Du, dass danach die 2 dran ist. Wenn Du mit der 1 nicht durch kommst, holst Du Dir den letzten aktuellen Zustand zurück und machst mit der 2 weiter.
    Usw.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ok, aber wie wird der alte Zustand gemerkt? Die Funktion ruft sich ja immer selbst auf, aber mit den selben Werten oder eben mit x+1.

    MB-Tech schrieb:

    aber wie wird der alte Zustand gemerkt?
    In einem Array, wo die bisherigen Werte abgelegt werden.
    In .NET würde ich dazu eine List(Of T) verwenden.
    Vielleicht entwickelst Du den Algo zunächst in .NET, bevor Du ihn nach native c / c++ überträgst.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich finde, so wirr, wie die Frage gestellt ist, kann man eiglich gar nicht helfen, ausser die Wirrsal zu spiegeln, und den TE auffordern, seine Frage klar zu stellen.

    Also was ich an "Informationen" empfangen habe ist:
    • 197 Zeilen c++ - Code, und wir sollen dir nun "auf die Sprünge helfen"
    • Das Zurückspringen ist echt irgendwie komisch.
    • Das Backtracking haste durchgelesen aber nicht zu 100% verstanden.
    • Wie genau kann man "zurückspringen"?
    • das Return 0 verwirrt dich
    Wie gesagt: Ich hab ühaupt keine Idee, wie man darauf sinnvoll antworten kann.



    Übrigens braucht man beim Backtracking mittels Rekursion nicht ständig eine List(Of T) mitzuführen.
    Diese Liste wird erst erstellt, wenn die erste Lösung gefunden wurde, und der Algo beendet - gewissermassen auf dem "Nachhauseweg", wenn alle verschachtelten Aufrufe returnen.
    Das ist glaub auch die Bedeutung des Begriffs "Back-Tracking" - "Rück-Verfolgung" (oder? mein Englisch ist nicht wirklich berühmt :/ )

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    197 Zeilen c++ - Code, und wir sollen dir nun "auf die Sprünge helfen"


    Dann konzentrieren wir uns auf die solve Funktion. Das ist ja der Knackpunkt.

    C-Quellcode

    1. //Sukoku Lösen
    2. int solveField(int x, int y)
    3. {
    4. if(x == G) //Zeilenende
    5. {
    6. y++;
    7. x = 0;
    8. }
    9. if(y == G)
    10. {
    11. //-> Solved
    12. return 1;
    13. }
    14. //Prüfen ob Feld gefüllt
    15. if(feld[y][x] > 0)
    16. {
    17. //Nächstes Feld
    18. return solveField(x+1, y);
    19. }
    20. //Zahl von 1-9 in aktuelles freies Feld einsetzen
    21. int z;
    22. for(z = 1; z < 10; z++)
    23. {
    24. //Zeilen prüfen - Spalten prüfen - 3er Päckchen prüfen
    25. if(checkSudoku(z,x,y) == 0)
    26. {
    27. feld[y][x] = z;
    28. //Auskommentieren zum Schrittweise anzeigen
    29. //printf("X: %d | Y: %d",x+1,y+1);
    30. //getch();
    31. //printField();
    32. if(solveField(x, y) == 1) //Gerade diese Zeile ist mir nicht ganz klar...
    33. {
    34. //Fertig -> Print
    35. printField();
    36. }
    37. } //else nächste Zahl z
    38. }
    39. feld[y][x] = 0; //Auch wenn die Zeile auf 0 gesetzt wird, dann ist doch x um +1 erhöht worden und es wäre jetzt ein Feld leer und übersprungen?
    40. return 0;
    41. }


    Das Zurückspringen ist wie gesagt noch meine Frage. Da bin ich noch nicht ganz hinter gestiegen.
    Ich erkläre mal soweit ich es verstanden habe:

    Funktion wird aufgerufen mit den x,y Werten 0. Danach prüft er, ob x der max Anzahl vom Array entspricht, falls ja y++.
    Falls y max Arraysize ist, dann das Sudokufeld komplett durchgelaufen und er returnt 1. (Könnte man hier nicht auch das "Ende" der Funktion setzen?
    Weil weiter unten wird ja abgefragt if(solveField(x,y) == 1)...?)

    Dann wird geprüft, ob etwas im Feld steht. Falls ja, dann rekrusiv aufrufen mit x+1 für das nächste Feld.
    Hat nichts drin gestanden, dann werden alle Zahlen von 1-9 ausprobiert. Sobald die Zahl die Sudokuregeln nicht verletzt wird diese genommen.
    Ergibt die Funktion 1, dann ist das Sudoku am Ende und das Feld wird ausgegeben. (Warum so und nicht direkt bei return 1 weiter oben?)

    Am Ende wird der Wert immer wieder auf 0 gesetzt und 0 returned. Warum?

    Ich hoffe meine Fragen sind nun klarer gestellt.

    Grüße
    ja, ist ein rekursiver versuch, aber mit logischem Fehler drin.

    Geplant ist scheinbar, dass die Methode 1 zurückgibt, wenn das sudoku gelöst ist.
    also müsste es beim if(checkSudoku()==0) noch einen else-Zweig geben, der 1 returnt, und damit die Suche abbricht.
    ebenfalls mit return 1 abgebrochen werden muss, wenn der rekursive Aufruf in #35 1 ergibt, denn dann ist ja eine Lösung gefunden.

    Wie du die Lösung dann präsentierst weiß ich nicht - das gehört auch nicht in die methode hinein - also das mit printfield wird glaub nix.
    Ja funktioniert. Deswegen bin ich ja so ratlos und frage die ganze Zeit...
    ah - jetzt sehe ich:
    #12: return 1;, wenn y die 10. Zeile erreicht hat. (ich nehme an G = 9)
    Dann nämlich sind alle 9 Zeilen erfolgreich ausgefüllt.

    Der Algo beendet beim ersten Erfolg nicht, sondern printet, und sucht dann weiter.

    Es werden die 9 Ziffern getestet, und wenn checkSudoku() eine Zahl als widerspruchsfrei attestiert wird diese ins feld eingetragen und der Selbst-Aufruf erfolgt mit erhöhtem Index.

    Also der Selbstaufruf erhöht den Index nur bei widerspruchsfrei eingetragener Zahl.
    Und 1 wird zurückgegeben, wenn der Index out of range ist - dann ist nämlich alles ausgefüllt.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    RushDen schrieb:

    Das kann gar nicht funktionieren, x und y werden nicht als Referenz übergeben (das wäre hier nämlich die einzige Möglichkeit, da der rekursive aufruf (also der zweite) mit den gleichen Parametern aufgerufen wird und es dadurch nie zu einer Abbruchbedingung kommen kann, selbst wenn eine gegeben wäre)


    Kopier dir den Source aus dem ersten Post mal in eine C IDE (Dev++ z.B.) und führe es aus. Es wird klappen.



    Der Selbstaufruf mit x+1 erfolgt doch nur, wenn ein Feld nicht leer ist, oder?

    Gruß

    Doppelpost zusammengefügt. ~Thunderbolt

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Thunderbolt“ ()

    naja, das ist schon sehr verschroben.
    Also wenn der Check erfolgreich, dann wird das feld gesetzt - #29 und er ruft sich selbst auf.
    Im Selbstaufruf dann stellter in #16 fest, dass das feld besetzt ist und ruft sich mit erhöhtem x auf.

    also ziemlich krank.

    Ich habs in vb bisserl gradlieniger:

    VB.NET-Quellcode

    1. Private Sub Solve()
    2. ReadFields()
    3. If Solve(0, 0) Then
    4. WriteFields(True)
    5. Else
    6. Msg("solve failed")
    7. End If
    8. End Sub
    9. Private Function TrySetValue(ByVal y As Integer, ByVal x As Integer, ByVal val As Integer) As Boolean
    10. For xx = 0 To 8
    11. If _Fields(y, xx) = val Then Return False
    12. Next
    13. For yy = 0 To 8
    14. If _Fields(yy, x) = val Then Return False
    15. Next
    16. Dim xBox = (x \ 3) * 3
    17. Dim yBox = (y \ 3) * 3
    18. For yy = yBox To yBox + 2
    19. For xx = xBox To xBox + 2
    20. If _Fields(yy, xx) = val Then Return False
    21. Next
    22. Next
    23. _Fields(y, x) = val
    24. Return True
    25. End Function
    26. Private Function Solve(ByVal y As Integer, ByVal x As Integer) As Boolean
    27. If x > 8 Then
    28. y += 1 : x = 0
    29. If y > 8 Then Return True 'finally all cells are set
    30. End If
    31. If _Fields(y, x) > 0 Then Return Solve(y, x + 1) 'user-set cell: recurse at once, skip digit-trying
    32. For val = 1 To 9
    33. 'try set 9 digits: On success recurse. if recursion also succeeds finish algo
    34. If TrySetValue(y, x, val) AndAlso Solve(y, x + 1) Then Return True
    35. Next
    36. 'no digit found: reset field + return false
    37. _Fields(y, x) = 0
    38. Return False
    39. End Function
    Diese Variante beendet aber den ganzen Algo, sobald die erste Lösung gefunden

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ok, also wenn Feld gesetzt und prüfung ok, dann x+1. das geht dann so lange bis ein widerspruch herauskommt. Dann müsste ja alles wieder Stufenweise zurück gehen, dieses ist mir aber noch nicht ersichtlich :/