C# :: Aufgabe #222

3 Lösungen Lösungen öffentlich

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ß!

Lösungen:

1 Kommentar
vote_ok
von Z3RP (1020 Punkte) - 21.08.2018 um 10:19 Uhr
Ich hätte eine Lösung aber sie ist nicht gut und Performant.

Quellcode ausblenden 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;
		}
	}
}
vote_ok
von hollst (13980 Punkte) - 04.09.2018 um 13:48 Uhr
Quellcode ausblenden C#-Code
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;
        }
    }
}
vote_ok
von RevTreb (860 Punkte) - 29.11.2018 um 15:59 Uhr
Hier mein Lösungsvorschlag.
Ich bin mir ziemlich sicher, daß ich das viel zu kompliziert angegangen bin :/

Program.cs
Quellcode ausblenden C#-Code
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
Quellcode ausblenden C#-Code
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
Quellcode ausblenden C#-Code
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
Quellcode ausblenden C#-Code
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
Quellcode ausblenden C#-Code
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
Quellcode ausblenden C#-Code
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();
        }
    }
}
1810220

Du scheinst einen AdBlocker zu nutzen. Ich würde mich freuen, wenn du ihn auf dieser Seite deaktivierst und dich davon überzeugst, dass die Werbung hier nicht störend ist.