C# :: Aufgabe #222
3 Lösungen
Generierung eines SUDOKU
Anfänger - C#
von hollst
- 13.08.2018 um 16:23 Uhr
Für alle, denen es nicht bekannt sein sollte: Ein SUDOKU ist ein 9 × 9 - Gitter,
das mit den Ziffern 1 bis 9 so ausgefüllt ist, dass jede Ziffer in jeder Spalte,
in jeder Zeile und in jedem 3 x 3 - Unterblock genau ein einziges Mal vorkommt (Bild 1).
Insgesamt sind fast 6.7 Trilliarden (10 hoch 21) unterschiedliche SUDOKUS konstruierbar
(genau 6.670.903.752.021.072.936.960).
Die Programmieraufgabe bestehe darin, mittels Zufallsgenerator SUDOKUS zu erzeugen
und anzuzeigen.
Durch Ausblendung einiger Felder entsteht aus einem SUDOKU ein SUDOKU-Rätsel. Dabei
müssen allerdings mindestens 17 Felder unausgeblendet sein, um eine eindeutige Lösung
zu erhalten (Beweis siehe McGuire, Gary; Tugemann, Bastian; Civario, Gilles:
There is no 16-Clue Sudoku: Solving the Sudoku Minimum Number of Clues Problem.
2012arXiv1201.0749M, Sep 2013). Die Erzeugung eines solchen SUDOKU-Rätsels sei
Fortgeschrittenen vorbehalten, da hierbei auch die Eindeutigkeit einer Lösung nachgewiesen
werden sollte. Der Gipfel wäre anschließend, einen computergestützten SUDOKU-Löser zu entwickeln;
aber, wir wollen es hier noch nicht übertreiben, eines nach dem anderen und nur, wer die
Herausforderung nich scheut.
Viel Spaß!
das mit den Ziffern 1 bis 9 so ausgefüllt ist, dass jede Ziffer in jeder Spalte,
in jeder Zeile und in jedem 3 x 3 - Unterblock genau ein einziges Mal vorkommt (Bild 1).
Insgesamt sind fast 6.7 Trilliarden (10 hoch 21) unterschiedliche SUDOKUS konstruierbar
(genau 6.670.903.752.021.072.936.960).
Die Programmieraufgabe bestehe darin, mittels Zufallsgenerator SUDOKUS zu erzeugen
und anzuzeigen.
Durch Ausblendung einiger Felder entsteht aus einem SUDOKU ein SUDOKU-Rätsel. Dabei
müssen allerdings mindestens 17 Felder unausgeblendet sein, um eine eindeutige Lösung
zu erhalten (Beweis siehe McGuire, Gary; Tugemann, Bastian; Civario, Gilles:
There is no 16-Clue Sudoku: Solving the Sudoku Minimum Number of Clues Problem.
2012arXiv1201.0749M, Sep 2013). Die Erzeugung eines solchen SUDOKU-Rätsels sei
Fortgeschrittenen vorbehalten, da hierbei auch die Eindeutigkeit einer Lösung nachgewiesen
werden sollte. Der Gipfel wäre anschließend, einen computergestützten SUDOKU-Löser zu entwickeln;
aber, wir wollen es hier noch nicht übertreiben, eines nach dem anderen und nur, wer die
Herausforderung nich scheut.
Viel Spaß!
Lösungen:
Ich hätte eine Lösung aber sie ist nicht gut und Performant.
C#-Code
using System;
using System.Collections.Generic;
namespace Sudoku
{
class Program
{
static int[,] grid = new int[9, 9];
static Random rnd = new Random();
static void Main(string[] args)
{
Console.WriteLine("Generating grid ...");
while (!GenerateGrid())
{
}
Console.Clear();
ShowGrid();
Console.ReadKey();
}
private static void ShowGrid()
{
for (int y = 0; y < 9; y++)
{
for (int x = 0; x < 9; x++)
{
Console.Write(grid[x, y]);
if((x+1) % 3 == 0)
Console.Write("|");
}
Console.WriteLine("");
if ((y + 1) % 3 == 0)
Console.WriteLine("------------");
}
}
private static bool GenerateGrid()
{
grid = new int[9, 9];
for (int i = 0; i < 9; i++)
{
grid[0, i] = rnd.Next(9)+1;
}
for (int y = 0; y < 9; y++)
{
for (int x = 1; x < 9; x++)
{
List<int> availableNumbers = Numbers();
for (int i = 0; i< 9; i++)
{
if (grid[i, y] != 0)
{
availableNumbers.Remove(grid[i, y]);
}
}
for (int i = 0; i < 9; i++)
{
if (grid[x, i] != 0)
{
availableNumbers.Remove(grid[x, i]);
}
}
if (availableNumbers.Count == 0)
{
return false;
}
grid[x, y] = availableNumbers[rnd.Next(availableNumbers.Count)];
}
}
return true;
}
private static List<int> Numbers()
{
List<int> numbers = new List<int>();
for (int i = 0; i < 9; i++)
{
numbers.Add(i + 1);
}
return numbers;
}
}
}
using System;
using static System.Console;
using System.Collections.Generic;
namespace aufgabe_222
{
class Program
{
static string NL = Environment.NewLine;
static void Main()
{
int leer_stellen = 45;
Random rand = new Random();
ConsoleKeyInfo ki;
do
{
Clear();
Sudoku_Class sudoku = new Sudoku_Class();
WriteLine(sudoku.grid.GridToString());
int counter_leer_stellen = 0;
while (counter_leer_stellen < leer_stellen)
{
int y = rand.Next(9), x = rand.Next(9);
if (sudoku.grid[y][x] != 0) {
sudoku.grid[y][x] = 0;
counter_leer_stellen++;
};
}
WriteLine("SUDOKU-Grid creation counter_loops: " + sudoku.counter_loops.ToString("n0"));
WriteLine(NL + sudoku.grid.GridToString());
WriteLine("should I solve (yes/no)?");
ki = ReadKey(false); WriteLine();
if (ki.Key == ConsoleKey.Y)
{
int solution_number = 0;
sudoku.solution(0, 0, sudoku.grid, 0, ref solution_number);
WriteLine("org_sudoku" + Environment.NewLine + sudoku.org_grid.GridToString());
string or_more = "(or more) ";
if (solution_number < 10)
or_more = string.Empty;
WriteLine($"sudoku puzzle has {solution_number} {or_more}solutions {NL}{sudoku.grid.GridToString()}");
}
WriteLine("exit ESC" + Environment.NewLine);
ki = ReadKey(false);
}
while (ki.Key != ConsoleKey.Escape);
}
}
public class Sudoku_Class
{
public byte[][] grid;
public byte[][] org_grid { private set; get; }
public int counter_loops = 0;
Random rand = new Random();
public Sudoku_Class() //CONSTRUCTOR
{
bool bo_okay = false;
while (!bo_okay)
{
this.grid = new byte[9][];
for (var i = 0; i < this.grid.Length; i++)
this.grid[i] = new byte[9];
int max_counter_not_ok = (int)1e+2; //100 ist "guter" wert
for (var i = 0; i < grid.Length; i++)
for (var j = 0; j < grid[i].Length; j++)
{
bo_okay = false;
int counter_not_ok = 0;
while (counter_not_ok < max_counter_not_ok && !bo_okay)
{
byte value = (byte)(rand.Next(9) + 1);
bo_okay = CheckSudokuValue(value, this.grid, i, j);
if (bo_okay)
this.grid[i][j] = value;
else
counter_not_ok++;
}
bool bo_test = counter_not_ok == max_counter_not_ok;
if (bo_test)
counter_loops++;
}
if (bo_okay) //check, ob grid vollständig belegt worden ist
for (var i = 0; i < grid.Length; i++)
for (var j = 0; j < grid[i].Length; j++)
if (grid[i][j] == 0)
bo_okay = false;
}
org_grid = grid.DeepCopy();
}
bool CheckSudokuValue(byte value, byte[][] grid, int row, int col)
{
for (var i = 0; i < grid.Length; i++)
if (grid[row][i] == value)
return false;
for (var i = 0; i < grid.Length; i++)
if (grid[i][col] == value)
return false;
byte br = (byte)(row / 3);
byte bc = (byte)(col / 3);
for (var i = 3 * br; i < 3 * (br + 1); i++)
for (var j = 3 * bc; j < 3 * (bc + 1); j++)
if (grid[i][j] == value)
return false;
return true;
}
//Rekursives Lösen
public void solution(int ab_iy, int ab_ix, byte[][] grid, int loop, ref int solution_number)
{
if (solution_number > 9)
return;
byte[][] grid_copy = grid.DeepCopy();
int[] next_empty = find_next_empty(ab_iy, ab_ix, grid_copy);
if (next_empty[0] < 0)
{
solution_number++;
WriteLine($"solution number {solution_number}");
WriteLine(grid_copy.GridToString());
return;
}
byte[] possibilities = find_possible_values(next_empty[0], next_empty[1], grid_copy);
if (possibilities.Length == 0)
return;
for (var i = 0; i < possibilities.Length; i++)
{
grid_copy[next_empty[0]][next_empty[1]] = possibilities[i];
solution(next_empty[0], next_empty[1], grid_copy, loop + 1, ref solution_number);
}
}
int[] find_next_empty(int ab_iy, int ab_ix, byte[][] grid, byte search_for = 0)
{
int[] result = new int[] { -1, -1 };
bool bo_found = false;
while (ab_iy < grid.Length && !bo_found)
{
if (grid[ab_iy][ab_ix] == search_for)
{
result = new int[] { ab_iy, ab_ix };
bo_found = true;
}
ab_ix++;
if (ab_ix == grid[ab_iy].Length)
{
ab_ix = 0;
ab_iy++;
}
}
return result;
}
byte[] find_possible_values(int row, int col, byte[][] grid)
{
List<byte> LB = new List<byte>();
for (var iv = 1; iv <= grid.Length; iv++)
{
bool bo_add = true;
for (var i = 0; i < grid.Length; i++)
{
if (grid[row][i] == iv)
bo_add = false;
if (grid[i][col] == iv)
bo_add = false;
}
int br = row / 3;
int bc = col / 3;
for (var i = 3 * br; i < 3 * (br + 1); i++)
for (var j = 3 * bc; j < 3 * (bc + 1); j++)
if (grid[i][j] == iv)
bo_add = false;
if (bo_add)
LB.Add((byte)iv);
}
return LB.ToArray();
}
}
public static class MyExtensions
{
public static string GridToString(this byte[][] grid)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (var i = 0; i < grid.Length; i++)
{
if (i % 3 == 0)
sb.AppendLine(" " + new String('-', 25));
for (var j = 0; j < grid[i].Length; j++)
{
if (j % 3 == 0)
sb.Append(" |");
if (grid[i][j] == 0)
sb.Append($" ");
else
sb.Append($"{grid[i][j],2}");
}
sb.AppendLine(" |");
}
sb.AppendLine(" " + new String('-', 25));
return sb.ToString();
}
public static string ToMyString<T>(this T[][] grid)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (var i = 0; i < grid.Length; i++)
{
sb.Append($"{i,3}: ");
for (var j = 0; j < grid[i].Length; j++)
sb.Append($"{grid[i][j],2}");
sb.AppendLine();
}
return sb.ToString();
}
public static string ToMyString<T>(this T[] grid, bool bo_nl = true)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (var i = 0; i < grid.Length; i++)
sb.Append($"{grid[i],2}");
if (bo_nl)
sb.AppendLine();
return sb.ToString();
}
public static T[][] DeepCopy<T>(this T[][] grid)
{
T[][] result = new T[grid.Length][];
for (var i = 0; i < grid.Length; i++)
{
result[i] = new T[grid[i].Length];
for (var j = 0; j < result[i].Length; j++)
result[i][j] = grid[i][j];
}
return result;
}
}
}
Hier mein Lösungsvorschlag.
Ich bin mir ziemlich sicher, daß ich das viel zu kompliziert angegangen bin :/
Program.cs
C#-Code
Sudoku.cs
C#-Code
SudokuBlock3x3.cs
C#-Code
SudokuCell.cs
C#-Code
SudokuRow.cs
C#-Code
SudokuField.cs
C#-Code
Ich bin mir ziemlich sicher, daß ich das viel zu kompliziert angegangen bin :/
Program.cs
using System;
namespace SudokuGenerator
{
class Program
{
static void Main(string[] args)
{
SudokuField field = new SudokuField();
field.Calc();
Console.WriteLine(field);
Console.WriteLine("nötige Versuche: " + Sudoku.stack);
Console.ReadLine();
}
}
}Sudoku.cs
using System;
using System.Collections.Generic;
namespace SudokuGenerator
{
static class Sudoku
{
public static int stack = 1;
public static Random rnd = new Random();
// Liefert die Differenzmenge aus Source - Used
public static List<int> Remaining(List<int> Used = null, List<int> Source = null)
{
if (Source==null)
Source = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
List<int> temp = new List<int>();
foreach (int i in Source)
if (!Used.Contains(i))
temp.Add(i);
return temp;
}
//Liefert zufällige Elemente aus der Menge <Source>
//... die Anzahl der Elemente kann mit <Count> begrenzt werden
public static List<int> Shuffle(List<int> Source = null, int Count=-1)
{
List<int> temp = new List<int>();
List<int> source = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
if (Source != null)
source = Source;
while (source.Count > 0)
{
int i = rnd.Next(1, source.Count + 1);
int nr = source[i - 1];
temp.Add(nr);
source.Remove(source.Find(o => o == nr));
}
while (Count>0 && temp.Count>Count)
temp.RemoveAt(0);
return temp;
}
// Liefert die Summe der zwei Mengen list1 und list2
public static List<int> Merge(List<int> list1, List<int> list2)
{
List<int> temp = new List<int>();
foreach (int i in list1)
temp.Add(i);
foreach (int i in list2)
if (!temp.Contains(i))
temp.Add(i);
return temp;
}
// Liefert die Summe der Elemente der drei Mengen list1, list2 und list3
public static List<int> Merge(List<int> list1, List<int> list2, List<int> list3)
{
return Merge(Merge(list1, list2),list3);
}
}
}
SudokuBlock3x3.cs
using System.Collections.Generic;
using System.Text;
namespace SudokuGenerator
{
class SudokuBlock3x3
{
#region Felder
SudokuCell[,] cells;
#endregion
#region Eigenschaft(en)
internal SudokuCell[,] Cells { get => cells; set => cells = value; }
#endregion
#region Konstruktor(en)
public SudokuBlock3x3()
{
cells = new SudokuCell[3, 3];
Init();
}
#endregion
internal void Init()
{
//In alle Zellen den Wert 0 eintragen
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
cells[x, y] = new SudokuCell(0);
//Zeilen und Spalten verlinken
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
{
if (y < 2)
cells[x, y].NextCellInColumn = cells[x, y + 1];
if (x < 2)
cells[x, y].NextCellInRow = cells[x + 1, y];
}
}
public static implicit operator List<int>(SudokuBlock3x3 block)
{
List<int> temp = new List<int>();
int content = 0;
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
{
content = block.cells[x, y].Content;
if (content != 0)
temp.Add(content);
}
return temp;
}
internal void Clear()
{
foreach (SudokuCell cell in cells)
cell.Clear();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
sb.AppendLine(cells[x,y] + " ");
return sb.ToString();
}
}
}
SudokuCell.cs
namespace SudokuGenerator
{
class SudokuCell
{
#region Felder
private int content;
private SudokuCell nextCellInRow;
private SudokuCell nextCellInColumn;
#endregion
#region Eigenschaft(en)
public int Content { get => content; set => content = value; }
internal SudokuCell NextCellInRow { get => nextCellInRow; set => nextCellInRow = value; }
internal SudokuCell NextCellInColumn { get => nextCellInColumn; set => nextCellInColumn = value; }
#endregion
#region Konstruktor(en)
public SudokuCell(int content)
{
this.content = content;
}
#endregion
internal void Clear()
{
content = 0;
}
public override string ToString()
{
return content.ToString();
}
}
}
SudokuRow.cs
using System.Collections.Generic;
namespace SudokuGenerator
{
class SudokuRow : List<SudokuCell>
{
public static implicit operator List<int>(SudokuRow Row)
{
List<int> temp = new List<int>();
foreach (SudokuCell cell in Row)
{
temp.Add(cell.Content);
}
return temp;
}
}
}
SudokuField.cs
using System;
using System.Collections.Generic;
using System.Text;
using static SudokuGenerator.Sudoku;
namespace SudokuGenerator
{
class SudokuField
{
#region Felder
private SudokuBlock3x3[,] blocks;
private SudokuRow[] rows;
private SudokuRow[] columns;
#endregion
#region Eigenschaft(en)
internal SudokuBlock3x3[,] Blocks { get => blocks; set => blocks = value; }
internal SudokuRow[] Rows { get => rows; set => rows = value; }
internal SudokuRow[] Columns { get => columns; set => columns = value; }
#endregion
#region Konstruktor(en)
public SudokuField()
{
blocks = new SudokuBlock3x3[3, 3];
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
blocks[x, y] = new SudokuBlock3x3();
Init();
}
#endregion
internal void Calc()
{
Clear(); // Wert jeder Zelle auf 0 setzen
try
{
FillField();
}
catch (ArgumentOutOfRangeException)
{
Sudoku.stack++;
if (Sudoku.stack%100==0)
Console.WriteLine(Sudoku.stack);
Calc();
}
}
private void Init()
{
//Blockübergänge verlinken
for (int i1 = 0; i1 < 2; i1++)
{
for (int i2 = 0; i2 < 3; i2++)
{
for (int t = 0; t < 3; t++)
{
blocks[i1, t].Cells[2, i2].NextCellInRow = blocks[i1 + 1, t].Cells[0, i2];
blocks[t, i1].Cells[i2, 2].NextCellInColumn = blocks[t, i1 + 1].Cells[i2, 0];
}
}
}
//verlinkte Zellen für besseres Handling in rows[] bzw. columns[] ablegen
rows = new SudokuRow[9];
columns = new SudokuRow[9];
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
rows[3 * y + x] = new SudokuRow();
columns[3 * x + y] = new SudokuRow();
SudokuCell cell = blocks[0, y].Cells[0, x];
while (cell != null)
{
rows[3 * y + x].Add(cell);
cell = cell.NextCellInRow;
}
cell = blocks[x, 0].Cells[y, 0];
while (cell != null)
{
columns[3 * x + y].Add(cell);
cell = cell.NextCellInColumn;
}
}
}
}
private void FillField()
{
//Ab hier wird mit Zahlen befüllt
List<int> used; //... sind im aktuellen Kontext bereits in Benutzung
List<int> remaining; //... dürfen im aktuellen Kontext verwendet werden
for (int by = 0; by < 3; by++) // ...für alle drei Blockzeilen
for (int bx = 0; bx < 3; bx++) //... und alle drei Blockspalten
for (int y = 0; y < 3; y++) // ... und allen Spalten im Block
for (int x = 0; x < 3; x++) // ... , sowie allen drei Zeilen im Block
{ // => also sprich für jede einzelne Sudokuzelle im aktuellen SudokuField
// links oben beginnend:
// Ermittle die Zahlenmenge <used>, die für die zu untersuchende Zelle nicht mehr in Frage kommen
// , weil sie in der Zeile, oder der Spalte, oder im eigenen Block bereits vorkommen.
used = Merge(columns[y + 3 * bx], rows[x + 3 * by], blocks[bx, by]);
// Ermittle aus der Restmenge eine zufällige Ziffer
remaining = Shuffle(Remaining(used), 1);
// Und trage sie in die aktuelle Zelle ein.
blocks[bx, by].Cells[y, x].Content = remaining[0];
}
}
//Überschreibt den Inhalt aller Zellen mit '0'
//Die Verlinkung bleibt jedoch bestehen
internal void Clear()
{
foreach (SudokuBlock3x3 block in blocks)
block.Clear();
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("┌───────┬───────┬───────┐");
for (int yb = 0; yb < 3; yb++)
{
for (int yc = 0; yc < 3; yc++)
{
for (int xb = 0; xb < 3; xb++)
{
sb.Append("│ ");
for (int xc = 0; xc < 3; xc++)
sb.Append(blocks[xb, yb].Cells[xc,yc] + " ");
}
sb.AppendLine("│ ");
}
if (yb<2)
sb.AppendLine("├───────┼───────┼───────┤");
}
sb.AppendLine("└───────┴───────┴───────┘");
return sb.ToString();
}
}
}