algoritme voor het oplossen van Sudoku

Ik wil een code in Python schrijven om een ​​Sudoku-puzzel op te lossen. Hebben jullie enig idee over een goed algoritme voor dit doel. Ik lees ergens in net over een algoritme dat het lost door het hele vak met alle mogelijke nummers op te vullen en voegt vervolgens bekende waarden in de overeenkomstige vakken in. Van de rij en coloumn van bekende waarden is de bekende waarde verwijderd. Als jullie beter zijn algoritme dan dit help me alsjeblieft om er een te schrijven. Ook ben ik in de war dat hoe ik de bekende waarden van de gebruiker moet lezen. Het is echt moeilijk om de waarden één voor één te betreden via console. Een eenvoudige manier voor dit anders dan het gebruik van GUI?


Antwoord 1, Autoriteit 100%

Hier is mijn Sudoku-oplosser in Python. Het maakt gebruik van eenvoudig backtracking-algoritme om de puzzel op te lossen.
Voor de eenvoud is er geen invoervaliden of fantastische uitvoer uitgevoerd. Het is de naakte minimumcode die het probleem oplost.

algoritme

  1. Zoek alle wettelijke waarden van een bepaalde cel
  2. Ga voor elke wettelijke waarde recursief en probeer het raster
  3. op te lossen

Oplossing

Het kost 9×9 grid gedeeltelijk gevuld met cijfers. Een cel met waarde 0 geeft aan dat deze niet is gevuld.

Code

def findNextCellToFill(grid, i, j):
        for x in range(i,9):
                for y in range(j,9):
                        if grid[x][y] == 0:
                                return x,y
        for x in range(0,9):
                for y in range(0,9):
                        if grid[x][y] == 0:
                                return x,y
        return -1,-1
def isValid(grid, i, j, e):
        rowOk = all([e != grid[i][x] for x in range(9)])
        if rowOk:
                columnOk = all([e != grid[x][j] for x in range(9)])
                if columnOk:
                        # finding the top left x,y co-ordinates of the section containing the i,j cell
                        secTopX, secTopY = 3 *(i//3), 3 *(j//3) #floored quotient should be used here. 
                        for x in range(secTopX, secTopX+3):
                                for y in range(secTopY, secTopY+3):
                                        if grid[x][y] == e:
                                                return False
                        return True
        return False
def solveSudoku(grid, i=0, j=0):
        i,j = findNextCellToFill(grid, i, j)
        if i == -1:
                return True
        for e in range(1,10):
                if isValid(grid,i,j,e):
                        grid[i][j] = e
                        if solveSudoku(grid, i, j):
                                return True
                        # Undo the current cell for backtracking
                        grid[i][j] = 0
        return False

De code testen


>>> input = [[5,1,7,6,0,0,0,3,4],[2,8,9,0,0,4,0,0,0],[3,4,6,2,0,5,0,9,0],[6,0,2,0,0,0,0,1,0],[0,3,8,0,0,6,0,4,7],[0,0,0,0,0,0,0,0,0],[0,9,0,0,0,0,0,7,8],[7,0,3,4,0,0,5,6,0],[0,0,0,0,0,0,0,0,0]]
>>> solveSudoku(input)
True
>>> input
[[5, 1, 7, 6, 9, 8, 2, 3, 4], [2, 8, 9, 1, 3, 4, 7, 5, 6], [3, 4, 6, 2, 7, 5, 8, 9, 1], [6, 7, 2, 8, 4, 9, 3, 1, 5], [1, 3, 8, 5, 2, 6, 9, 4, 7], [9, 5, 4, 7, 1, 3, 6, 8, 2], [4, 9, 5, 3, 6, 2, 1, 7, 8], [7, 2, 3, 4, 8, 1, 5, 6, 9], [8, 6, 1, 9, 5, 7, 4, 2, 3]]

De bovenstaande is een zeer eenvoudig backtracking-algoritme dat op veel plaatsen wordt uitgelegd. Maar de meest interessante en natuurlijke van de sudoku-oplosstrategieën die ik tegenkwam, is dezevan hier


Antwoord 2, autoriteit 24%

Ik heb ook een Sudoku-oplosser geschreven in Python. Het is ook een backtracking-algoritme, maar ik wilde mijn implementatie ook delen.

Backtracking kan snel genoeg zijn, aangezien het binnen de beperkingen beweegt en cellen verstandig kiest. Misschien wil je ook mijn antwoord in deze thread over het optimaliseren van het algoritmebekijken. Maar hier zal ik me concentreren op het algoritme en de code zelf.

De essentie van het algoritme is om het raster te herhalen en beslissingen te nemen over wat te doen – een cel vullen, of een ander cijfer voor dezelfde cel proberen, of een cel leegmaken en teruggaan naar de vorige cel, enz. Het is belangrijk op te merken dat er geen deterministische manier is om te weten hoeveel stappen of iteraties je nodig hebt om de puzzel op te lossen. Daarom heb je eigenlijk twee opties – een while-lus gebruiken of recursie gebruiken. Beiden kunnen doorgaan met itereren totdat een oplossing is gevonden of totdat een gebrek aan een oplossing is bewezen. Het voordeel van de recursie is dat het kan vertakken en over het algemeen complexere logica’s en algoritmen ondersteunt, maar het nadeel is dat het moeilijker te implementeren is en vaak lastig te debuggen. Voor mijn implementatie van de backtracking heb ik een while-lus gebruikt omdat er geen vertakking nodig is, het algoritme zoekt op een lineaire manier met één thread.

De logica gaat als volgt:

Terwijl True: (belangrijkste herhalingen)

  1. Als alle blanco cellen zijn geïllustreerd en de laatste lege cel herhaalt geen resterende cijfers die moeten worden geprobeerd – stop hier omdat er geen oplossing is.
  2. Als er geen blanco cellen zijn, valideren het raster. Als het raster hier geldig is en de oplossing retourneert.
  3. Als er blanco cellen zijn, kies dan de volgende cel. Als die cel op zijn minst op mogelijk cijfer heeft, wijst u het toe en gaat u verder met de volgende hoofd iteratie.
  4. Als er ten minste één resterende keuze is voor de huidige cel en zijn er geen blanco cellen of zijn er alle blanco cellen geïllustreerd, wijs de resterende keuze toe en gaan door naar de volgende belangrijkste iteratie.
  5. Als geen van het bovenstaande waar is, is het tijd om te backtrack. Blank uit de huidige cel en voer de onderste lus in.

Terwijl TRUE: (backtrack iterations)

  1. Als er geen cellen meer zijn om te backtrack om hier te stoppen, want daar
    is geen oplossing.
  2. Selecteer de vorige cel volgens de backtracking-geschiedenis.
  3. Als de cel geen keuzes over heeft, blank de cel en
    Ga door naar de volgende backtrack-iteratie.
  4. Wijs het volgende beschikbare cijfer toe aan de huidige cel, breek uit van
    backtracking en keer terug naar de belangrijkste iteraties.

Enkele functies van het algoritme:

  • Het houdt een record van de bezochte cellen in dezelfde volgorde, zodat deze op elk moment

  • kan backtrack

  • Het houdt een verslag van keuzes voor elke cel zodat het niet twee keer hetzelfde cijfer voor dezelfde cel probeert

  • De beschikbare keuzes voor een cel staan ​​altijd binnen de Sudoku-beperkingen (rij, kolom en 3×3 kwadrant)

  • Deze specifieke implementatie heeft een paar verschillende methoden om de volgende cel en het volgende cijfer te kiezen, afhankelijk van de invoerparameters (meer info in de optimalisatiedraad )

  • Als een leeg raster wordt gegeven, genereert het een geldige Sudoku-puzzel (gebruik met optimalisatieparameter “C” om elke keer een willekeurig raster te genereren)

  • als het een opgelost raster krijgt, zal het het herkennen en een bericht afdrukken

De volledige code is:

import random, math, time
class Sudoku:
    def __init__( self, _g=[] ):
        self._input_grid = [] # store a copy of the original input grid for later use
        self.grid = [] # this is the main grid that will be iterated
        for i in _g: # copy the nested lists by value, otherwise Python keeps the reference for the nested lists
            self._input_grid.append( i[:] )
            self.grid.append( i[:] )
    self.empty_cells = set() # set of all currently empty cells (by index number from left to right, top to bottom)
    self.empty_cells_initial = set() # this will be used to compare against the current set of empty cells in order to determine if all cells have been iterated
    self.current_cell = None # used for iterating
    self.current_choice = 0 # used for iterating
    self.history = [] # list of visited cells for backtracking
    self.choices = {} # dictionary of sets of currently available digits for each cell
    self.nextCellWeights = {} # a dictionary that contains weights for all cells, used when making a choice of next cell
    self.nextCellWeights_1 = lambda x: None # the first function that will be called to assign weights
    self.nextCellWeights_2 = lambda x: None # the second function that will be called to assign weights
    self.nextChoiceWeights = {} # a dictionary that contains weights for all choices, used when selecting the next choice
    self.nextChoiceWeights_1 = lambda x: None # the first function that will be called to assign weights
    self.nextChoiceWeights_2 = lambda x: None # the second function that will be called to assign weights
    self.search_space = 1 # the number of possible combinations among the empty cells only, for information purpose only
    self.iterations = 0 # number of main iterations, for information purpose only
    self.iterations_backtrack = 0 # number of backtrack iterations, for information purpose only
    self.digit_heuristic = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0 } # store the number of times each digit is used in order to choose the ones that are least/most used, parameter "3" and "4"
    self.centerWeights = {} # a dictionary of the distances for each cell from the center of the grid, calculated only once at the beginning
    # populate centerWeights by using Pythagorean theorem
    for id in range( 81 ):
        row = id // 9
        col = id % 9
        self.centerWeights[ id ] = int( round( 100 * math.sqrt( (row-4)**2 + (col-4)**2 ) ) )
    # for debugging purposes
    def dump( self, _custom_text, _file_object ):
        _custom_text += ", cell: {}, choice: {}, choices: {}, empty: {}, history: {}, grid: {}\n".format(
            self.current_cell, self.current_choice, self.choices, self.empty_cells, self.history, self.grid )
        _file_object.write( _custom_text )
    # to be called before each solve of the grid
    def reset( self ):
        self.grid = []
        for i in self._input_grid:
            self.grid.append( i[:] )
        self.empty_cells = set()
        self.empty_cells_initial = set()
        self.current_cell = None
        self.current_choice = 0
        self.history = []
        self.choices = {}
        self.nextCellWeights = {}
        self.nextCellWeights_1 = lambda x: None
        self.nextCellWeights_2 = lambda x: None
        self.nextChoiceWeights = {}
        self.nextChoiceWeights_1 = lambda x: None
        self.nextChoiceWeights_2 = lambda x: None
        self.search_space = 1
        self.iterations = 0
        self.iterations_backtrack = 0
        self.digit_heuristic = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0 }
    def validate( self ):
        # validate all rows
        for x in range(9):
            digit_count = { 0:1, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0 }
            for y in range(9):
                digit_count[ self.grid[ x ][ y ] ] += 1
            for i in digit_count:
                if digit_count[ i ] != 1:
                    return False
        # validate all columns
        for x in range(9):
            digit_count = { 0:1, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0 }
            for y in range(9):
                digit_count[ self.grid[ y ][ x ] ] += 1
            for i in digit_count:
                if digit_count[ i ] != 1:
                    return False
        # validate all 3x3 quadrants
        def validate_quadrant( _grid, from_row, to_row, from_col, to_col ):
            digit_count = { 0:1, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0 }
            for x in range( from_row, to_row + 1 ):
                for y in range( from_col, to_col + 1 ):
                    digit_count[ _grid[ x ][ y ] ] += 1
            for i in digit_count:
                if digit_count[ i ] != 1:
                    return False
            return True
        for x in range( 0, 7, 3 ):
            for y in range( 0, 7, 3 ):
                if not validate_quadrant( self.grid, x, x+2, y, y+2 ):
                    return False
        return True
    def setCell( self, _id, _value ):
        row = _id // 9
        col = _id % 9
        self.grid[ row ][ col ] = _value
    def getCell( self, _id ):
        row = _id // 9
        col = _id % 9
        return self.grid[ row ][ col ]
    # returns a set of IDs of all blank cells that are related to the given one, related means from the same row, column or quadrant
    def getRelatedBlankCells( self, _id ):
        result = set()
        row = _id // 9
        col = _id % 9
        for i in range( 9 ):
            if self.grid[ row ][ i ] == 0: result.add( row * 9 + i )
        for i in range( 9 ):
            if self.grid[ i ][ col ] == 0: result.add( i * 9 + col )
        for x in range( (row//3)*3, (row//3)*3 + 3 ):
            for y in range( (col//3)*3, (col//3)*3 + 3 ):
                if self.grid[ x ][ y ] == 0: result.add( x * 9 + y )
        return set( result ) # return by value
    # get the next cell to iterate
    def getNextCell( self ):
        self.nextCellWeights = {}
        for id in self.empty_cells:
            self.nextCellWeights[ id ] = 0
        self.nextCellWeights_1( 1000 ) # these two functions will always be called, but behind them will be a different weight function depending on the optimization parameters provided
        self.nextCellWeights_2( 1 )
        return min( self.nextCellWeights, key = self.nextCellWeights.get )
    def nextCellWeights_A( self, _factor ): # the first cell from left to right, from top to bottom
        for id in self.nextCellWeights:
            self.nextCellWeights[ id ] += id * _factor
    def nextCellWeights_B( self, _factor ): # the first cell from right to left, from bottom to top
        self.nextCellWeights_A( _factor * -1 )
    def nextCellWeights_C( self, _factor ): # a randomly chosen cell
        for id in self.nextCellWeights:
            self.nextCellWeights[ id ] += random.randint( 0, 999 ) * _factor
    def nextCellWeights_D( self, _factor ): # the closest cell to the center of the grid
        for id in self.nextCellWeights:
            self.nextCellWeights[ id ] += self.centerWeights[ id ] * _factor
    def nextCellWeights_E( self, _factor ): # the cell that currently has the fewest choices available
        for id in self.nextCellWeights:
            self.nextCellWeights[ id ] += len( self.getChoices( id ) ) * _factor
    def nextCellWeights_F( self, _factor ): # the cell that currently has the most choices available
        self.nextCellWeights_E( _factor * -1 )
    def nextCellWeights_G( self, _factor ): # the cell that has the fewest blank related cells
        for id in self.nextCellWeights:
            self.nextCellWeights[ id ] += len( self.getRelatedBlankCells( id ) ) * _factor
    def nextCellWeights_H( self, _factor ): # the cell that has the most blank related cells
        self.nextCellWeights_G( _factor * -1 )
    def nextCellWeights_I( self, _factor ): # the cell that is closest to all filled cells
        for id in self.nextCellWeights:
            weight = 0
            for check in range( 81 ):
                if self.getCell( check ) != 0:
                    weight += math.sqrt( ( id//9 - check//9 )**2 + ( id%9 - check%9 )**2 )
    def nextCellWeights_J( self, _factor ): # the cell that is furthest from all filled cells
        self.nextCellWeights_I( _factor * -1 )
    def nextCellWeights_K( self, _factor ): # the cell whose related blank cells have the fewest available choices
        for id in self.nextCellWeights:
            weight = 0
            for id_blank in self.getRelatedBlankCells( id ):
                weight += len( self.getChoices( id_blank ) )
            self.nextCellWeights[ id ] += weight * _factor
    def nextCellWeights_L( self, _factor ): # the cell whose related blank cells have the most available choices
        self.nextCellWeights_K( _factor * -1 )
    # for a given cell return a set of possible digits within the Sudoku restrictions
    def getChoices( self, _id ):
        available_choices = {1,2,3,4,5,6,7,8,9}
        row = _id // 9
        col = _id % 9
        # exclude digits from the same row
        for y in range( 0, 9 ):
            if self.grid[ row ][ y ] in available_choices:
                available_choices.remove( self.grid[ row ][ y ] )
        # exclude digits from the same column
        for x in range( 0, 9 ):
            if self.grid[ x ][ col ] in available_choices:
                available_choices.remove( self.grid[ x ][ col ] )
        # exclude digits from the same quadrant
        for x in range( (row//3)*3, (row//3)*3 + 3 ):
            for y in range( (col//3)*3, (col//3)*3 + 3 ):
                if self.grid[ x ][ y ] in available_choices:
                    available_choices.remove( self.grid[ x ][ y ] )
        if len( available_choices ) == 0: return set()
        else: return set( available_choices ) # return by value
    def nextChoice( self ):
        self.nextChoiceWeights = {}
        for i in self.choices[ self.current_cell ]:
            self.nextChoiceWeights[ i ] = 0
        self.nextChoiceWeights_1( 1000 )
        self.nextChoiceWeights_2( 1 )
        self.current_choice = min( self.nextChoiceWeights, key = self.nextChoiceWeights.get )
        self.setCell( self.current_cell, self.current_choice )
        self.choices[ self.current_cell ].remove( self.current_choice )
    def nextChoiceWeights_0( self, _factor ): # the lowest digit
        for i in self.nextChoiceWeights:
            self.nextChoiceWeights[ i ] += i * _factor
    def nextChoiceWeights_1( self, _factor ): # the highest digit
        self.nextChoiceWeights_0( _factor * -1 )
    def nextChoiceWeights_2( self, _factor ): # a randomly chosen digit
        for i in self.nextChoiceWeights:
            self.nextChoiceWeights[ i ] += random.randint( 0, 999 ) * _factor
    def nextChoiceWeights_3( self, _factor ): # heuristically, the least used digit across the board
        self.digit_heuristic = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0 }
        for id in range( 81 ):
            if self.getCell( id ) != 0: self.digit_heuristic[ self.getCell( id ) ] += 1
        for i in self.nextChoiceWeights:
            self.nextChoiceWeights[ i ] += self.digit_heuristic[ i ] * _factor
    def nextChoiceWeights_4( self, _factor ): # heuristically, the most used digit across the board
        self.nextChoiceWeights_3( _factor * -1 )
    def nextChoiceWeights_5( self, _factor ): # the digit that will cause related blank cells to have the least number of choices available
        cell_choices = {}
        for id in self.getRelatedBlankCells( self.current_cell ):
            cell_choices[ id ] = self.getChoices( id )
        for c in self.nextChoiceWeights:
            weight = 0
            for id in cell_choices:
                weight += len( cell_choices[ id ] )
                if c in cell_choices[ id ]: weight -= 1
            self.nextChoiceWeights[ c ] += weight * _factor
    def nextChoiceWeights_6( self, _factor ): # the digit that will cause related blank cells to have the most number of choices available
        self.nextChoiceWeights_5( _factor * -1 )
    def nextChoiceWeights_7( self, _factor ): # the digit that is the least common available choice among related blank cells
        cell_choices = {}
        for id in self.getRelatedBlankCells( self.current_cell ):
            cell_choices[ id ] = self.getChoices( id )
        for c in self.nextChoiceWeights:
            weight = 0
            for id in cell_choices:
                if c in cell_choices[ id ]: weight += 1
            self.nextChoiceWeights[ c ] += weight * _factor
    def nextChoiceWeights_8( self, _factor ): # the digit that is the most common available choice among related blank cells
        self.nextChoiceWeights_7( _factor * -1 )
    def nextChoiceWeights_9( self, _factor ): # the digit that is the least common available choice across the board
        cell_choices = {}
        for id in range( 81 ):
            if self.getCell( id ) == 0:
                cell_choices[ id ] = self.getChoices( id )
        for c in self.nextChoiceWeights:
            weight = 0
            for id in cell_choices:
                if c in cell_choices[ id ]: weight += 1
            self.nextChoiceWeights[ c ] += weight * _factor
    def nextChoiceWeights_a( self, _factor ): # the digit that is the most common available choice across the board
        self.nextChoiceWeights_9( _factor * -1 )
    # the main function to be called
    def solve( self, _nextCellMethod, _nextChoiceMethod, _start_time, _prefillSingleChoiceCells = False ):
        s = self
        s.reset()
        # initialize optimization functions based on the optimization parameters provided
        """
        A - the first cell from left to right, from top to bottom
        B - the first cell from right to left, from bottom to top
        C - a randomly chosen cell
        D - the closest cell to the center of the grid
        E - the cell that currently has the fewest choices available
        F - the cell that currently has the most choices available
        G - the cell that has the fewest blank related cells
        H - the cell that has the most blank related cells
        I - the cell that is closest to all filled cells
        J - the cell that is furthest from all filled cells
        K - the cell whose related blank cells have the fewest available choices
        L - the cell whose related blank cells have the most available choices
        """
        if _nextCellMethod[ 0 ] in "ABCDEFGHIJKLMN":
            s.nextCellWeights_1 = getattr( s, "nextCellWeights_" + _nextCellMethod[0] )
        elif _nextCellMethod[ 0 ] == " ":
            s.nextCellWeights_1 = lambda x: None
        else:
            print( "(A) Incorrect optimization parameters provided" )
            return False
        if len( _nextCellMethod ) > 1:
            if _nextCellMethod[ 1 ] in "ABCDEFGHIJKLMN":
                s.nextCellWeights_2 = getattr( s, "nextCellWeights_" + _nextCellMethod[1] )
            elif _nextCellMethod[ 1 ] == " ":
                s.nextCellWeights_2 = lambda x: None
            else:
                print( "(B) Incorrect optimization parameters provided" )
                return False
        else:
            s.nextCellWeights_2 = lambda x: None
        # initialize optimization functions based on the optimization parameters provided
        """
        0 - the lowest digit
        1 - the highest digit
        2 - a randomly chosen digit
        3 - heuristically, the least used digit across the board
        4 - heuristically, the most used digit across the board
        5 - the digit that will cause related blank cells to have the least number of choices available
        6 - the digit that will cause related blank cells to have the most number of choices available
        7 - the digit that is the least common available choice among related blank cells
        8 - the digit that is the most common available choice among related blank cells
        9 - the digit that is the least common available choice across the board
        a - the digit that is the most common available choice across the board
        """
        if _nextChoiceMethod[ 0 ] in "0123456789a":
            s.nextChoiceWeights_1 = getattr( s, "nextChoiceWeights_" + _nextChoiceMethod[0] )
        elif _nextChoiceMethod[ 0 ] == " ":
            s.nextChoiceWeights_1 = lambda x: None
        else:
            print( "(C) Incorrect optimization parameters provided" )
            return False
        if len( _nextChoiceMethod ) > 1:
            if _nextChoiceMethod[ 1 ] in "0123456789a":
                s.nextChoiceWeights_2 = getattr( s, "nextChoiceWeights_" + _nextChoiceMethod[1] )
            elif _nextChoiceMethod[ 1 ] == " ":
                s.nextChoiceWeights_2 = lambda x: None
            else:
                print( "(D) Incorrect optimization parameters provided" )
                return False
        else:
            s.nextChoiceWeights_2 = lambda x: None
        # fill in all cells that have single choices only, and keep doing it until there are no left, because as soon as one cell is filled this might bring the choices down to 1 for another cell
        if _prefillSingleChoiceCells == True:
            while True:
                next = False
                for id in range( 81 ):
                    if s.getCell( id ) == 0:
                        cell_choices = s.getChoices( id )
                        if len( cell_choices ) == 1:
                            c = cell_choices.pop()
                            s.setCell( id, c )
                            next = True
                if not next: break
        # initialize set of empty cells
        for x in range( 0, 9, 1 ):
            for y in range( 0, 9, 1 ):
                if s.grid[ x ][ y ] == 0:
                    s.empty_cells.add( 9*x + y )
        s.empty_cells_initial = set( s.empty_cells ) # copy by value
        # calculate search space
        for id in s.empty_cells:
            s.search_space *= len( s.getChoices( id ) )
        # initialize the iteration by choosing a first cell
        if len( s.empty_cells ) < 1:
            if s.validate():
                print( "Sudoku provided is valid!" )
                return True
            else:
                print( "Sudoku provided is not valid!" )
                return False
        else: s.current_cell = s.getNextCell()
        s.choices[ s.current_cell ] = s.getChoices( s.current_cell )
        if len( s.choices[ s.current_cell ] ) < 1:
            print( "(C) Sudoku cannot be solved!" )
            return False
        # start iterating the grid
        while True:
            #if time.time() - _start_time > 2.5: return False # used when doing mass tests and don't want to wait hours for an inefficient optimization to complete
            s.iterations += 1
            # if all empty cells and all possible digits have been exhausted, then the Sudoku cannot be solved
            if s.empty_cells == s.empty_cells_initial and len( s.choices[ s.current_cell ] ) < 1:
                print( "(A) Sudoku cannot be solved!" )
                return False
            # if there are no empty cells, it's time to validate the Sudoku
            if len( s.empty_cells ) < 1:
                if s.validate():
                    print( "Sudoku has been solved! " )
                    print( "search space is {}".format( self.search_space ) )
                    print( "empty cells: {}, iterations: {}, backtrack iterations: {}".format( len( self.empty_cells_initial ), self.iterations, self.iterations_backtrack ) )
                    for i in range(9):
                        print( self.grid[i] )
                    return True
            # if there are empty cells, then move to the next one
            if len( s.empty_cells ) > 0:
                s.current_cell = s.getNextCell() # get the next cell
                s.history.append( s.current_cell ) # add the cell to history
                s.empty_cells.remove( s.current_cell ) # remove the cell from the empty queue
                s.choices[ s.current_cell ] = s.getChoices( s.current_cell ) # get possible choices for the chosen cell
                if len( s.choices[ s.current_cell ] ) > 0: # if there is at least one available digit, then choose it and move to the next iteration, otherwise the iteration continues below with a backtrack
                    s.nextChoice()
                    continue
            # if all empty cells have been iterated or there are no empty cells, and there are still some remaining choices, then try another choice
            if len( s.choices[ s.current_cell ] ) > 0 and ( s.empty_cells == s.empty_cells_initial or len( s.empty_cells ) < 1 ): 
                s.nextChoice()
                continue
            # if none of the above, then we need to backtrack to a cell that was previously iterated
            # first, restore the current cell...
            s.history.remove( s.current_cell ) # ...by removing it from history
            s.empty_cells.add( s.current_cell ) # ...adding back to the empty queue
            del s.choices[ s.current_cell ] # ...scrapping all choices
            s.current_choice = 0
            s.setCell( s.current_cell, s.current_choice ) # ...and blanking out the cell
            # ...and then, backtrack to a previous cell
            while True:
                s.iterations_backtrack += 1
                if len( s.history ) < 1:
                    print( "(B) Sudoku cannot be solved!" )
                    return False
                s.current_cell = s.history[ -1 ] # after getting the previous cell, do not recalculate all possible choices because we will lose the information about has been tried so far
                if len( s.choices[ s.current_cell ] ) < 1: # backtrack until a cell is found that still has at least one unexplored choice...
                    s.history.remove( s.current_cell )
                    s.empty_cells.add( s.current_cell )
                    s.current_choice = 0
                    del s.choices[ s.current_cell ]
                    s.setCell( s.current_cell, s.current_choice )
                    continue
                # ...and when such cell is found, iterate it
                s.nextChoice()
                break # and break out from the backtrack iteration but will return to the main iteration

Voorbeeld van bellen met ‘s werelds moeilijkste Sudoku volgens dit artikel http://www.telegraph.co.uk/news/science/science-news/9359579/Worlds-hardest-sudoku-can-you-crack-it.html

hardest_sudoku = [
    [8,0,0,0,0,0,0,0,0],
    [0,0,3,6,0,0,0,0,0],
    [0,7,0,0,9,0,2,0,0],
    [0,5,0,0,0,7,0,0,0],
    [0,0,0,0,4,5,7,0,0],
    [0,0,0,1,0,0,0,3,0],
    [0,0,1,0,0,0,0,6,8],
    [0,0,8,5,0,0,0,1,0],
    [0,9,0,0,0,0,4,0,0]]
mySudoku = Sudoku( hardest_sudoku )
start = time.time()
mySudoku.solve( "A", "0", time.time(), False )
print( "solved in {} seconds".format( time.time() - start ) )

En voorbeelduitvoer is:

Sudoku has been solved!
search space is 9586591201964851200000000000000000000
empty cells: 60, iterations: 49559, backtrack iterations: 49498
[8, 1, 2, 7, 5, 3, 6, 4, 9]
[9, 4, 3, 6, 8, 2, 1, 7, 5]
[6, 7, 5, 4, 9, 1, 2, 8, 3]
[1, 5, 4, 2, 3, 7, 8, 9, 6]
[3, 6, 9, 8, 4, 5, 7, 2, 1]
[2, 8, 7, 1, 6, 9, 5, 3, 4]
[5, 2, 1, 9, 7, 4, 3, 6, 8]
[4, 3, 8, 5, 2, 6, 9, 1, 7]
[7, 9, 6, 3, 1, 8, 4, 5, 2]
solved in 1.1600663661956787 seconds

Antwoord 3, autoriteit 24%

Hier is een veel snellere oplossing gebaseerd op hari’s antwoord. Het fundamentele verschil is dat we een reeks mogelijke waarden bewaren voor cellen waaraan geen waarde is toegewezen. Dus als we een nieuwe waarde proberen, proberen we alleen geldige waarden en we propageren ook wat deze keuze betekent voor de rest van de sudoku. In de propagatiestap verwijderen we uit de reeks geldige waarden voor elke cel de waarden die al in de rij, kolom of hetzelfde blok voorkomen. Als er nog maar één getal in de set over is, weten we dat de positie (cel) die waarde moet hebben.

Deze methode staat bekend als vooruitkijken en vooruitkijken (http:/ /ktiml.mff.cuni.cz/~bartak/constraints/propagation.html).

De onderstaande implementatie heeft één iteratie nodig (calls of solve) terwijl de implementatie van hari 487 nodig heeft. Natuurlijk is mijn code wat langer. De propageermethode is ook niet optimaal.

import sys
from copy import deepcopy
def output(a):
    sys.stdout.write(str(a))
N = 9
field = [[5,1,7,6,0,0,0,3,4],
         [2,8,9,0,0,4,0,0,0],
         [3,4,6,2,0,5,0,9,0],
         [6,0,2,0,0,0,0,1,0],
         [0,3,8,0,0,6,0,4,7],
         [0,0,0,0,0,0,0,0,0],
         [0,9,0,0,0,0,0,7,8],
         [7,0,3,4,0,0,5,6,0],
         [0,0,0,0,0,0,0,0,0]]
def print_field(field):
    if not field:
        output("No solution")
        return
    for i in range(N):
        for j in range(N):
            cell = field[i][j]
            if cell == 0 or isinstance(cell, set):
                output('.')
            else:
                output(cell)
            if (j + 1) % 3 == 0 and j < 8:
                output(' |')
            if j != 8:
                output(' ')
        output('\n')
        if (i + 1) % 3 == 0 and i < 8:
            output("- - - + - - - + - - -\n")
def read(field):
    """ Read field into state (replace 0 with set of possible values) """
    state = deepcopy(field)
    for i in range(N):
        for j in range(N):
            cell = state[i][j]
            if cell == 0:
                state[i][j] = set(range(1,10))
    return state
state = read(field)
def done(state):
    """ Are we done? """
    for row in state:
        for cell in row:
            if isinstance(cell, set):
                return False
    return True
def propagate_step(state):
    """
    Propagate one step.
    @return:  A two-tuple that says whether the configuration
              is solvable and whether the propagation changed
              the state.
    """
            new_units = False
    # propagate row rule
    for i in range(N):
        row = state[i]
        values = set([x for x in row if not isinstance(x, set)])
        for j in range(N):
            if isinstance(state[i][j], set):
                state[i][j] -= values
                if len(state[i][j]) == 1:
                    val = state[i][j].pop()
                    state[i][j] = val
                    values.add(val)
                    new_units = True
                elif len(state[i][j]) == 0:
                    return False, None
    # propagate column rule
    for j in range(N):
        column = [state[x][j] for x in range(N)]
        values = set([x for x in column if not isinstance(x, set)])
        for i in range(N):
            if isinstance(state[i][j], set):
                state[i][j] -= values
                if len(state[i][j]) == 1:
                    val = state[i][j].pop()
                    state[i][j] = val
                    values.add(val)
                    new_units = True
                elif len(state[i][j]) == 0:
                    return False, None
    # propagate cell rule
    for x in range(3):
        for y in range(3):
            values = set()
            for i in range(3 * x, 3 * x + 3):
                for j in range(3 * y, 3 * y + 3):
                    cell = state[i][j]
                    if not isinstance(cell, set):
                        values.add(cell)
            for i in range(3 * x, 3 * x + 3):
                for j in range(3 * y, 3 * y + 3):
                    if isinstance(state[i][j], set):
                        state[i][j] -= values
                        if len(state[i][j]) == 1:
                            val = state[i][j].pop()
                            state[i][j] = val
                            values.add(val)
                            new_units = True
                        elif len(state[i][j]) == 0:
                            return False, None
    return True, new_units
def propagate(state):
    """ Propagate until we reach a fixpoint """
    while True:
        solvable, new_unit = propagate_step(state)
        if not solvable:
            return False
        if not new_unit:
            return True
def solve(state):
    """ Solve sudoku """
    solvable = propagate(state)
    if not solvable:
        return None
    if done(state):
        return state
    for i in range(N):
        for j in range(N):
            cell = state[i][j]
            if isinstance(cell, set):
                for value in cell:
                    new_state = deepcopy(state)
                    new_state[i][j] = value
                    solved = solve(new_state)
                    if solved is not None:
                        return solved
                return None
print_field(solve(state))

Antwoord 4, autoriteit 15%

Ik heb een eenvoudig programma geschreven dat de makkelijke oploste. Het haalde zijn invoer uit een bestand dat slechts een matrix was met spaties en cijfers. De datastructuur om het op te lossen was slechts een 9 bij 9 matrix van een bitmasker. Het bitmasker zou aangeven welke nummers nog mogelijk waren op een bepaalde positie. Als u de getallen uit het bestand invult, worden de getallen in alle rijen/kolommen naast elke bekende locatie kleiner. Als dat is gebeurd, blijf je de matrix herhalen en mogelijke getallen verkleinen. Als elke locatie nog maar één optie heeft, bent u klaar. Maar er zijn enkele sudoku’s die meer werk nodig hebben. Voor deze kun je gewoon brute kracht gebruiken: probeer alle resterende mogelijke combinaties totdat je er een vindt die werkt.


Antwoord 5, autoriteit 9%

Ik weet dat ik laat ben, maar dit is mijn versie:

from time import perf_counter
board = [
    [8, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 3, 6, 0, 0, 0, 0, 0],
    [0, 7, 0, 0, 9, 0, 2, 0, 0],
    [0, 5, 0, 0, 0, 7, 0, 0, 0],
    [0, 0, 0, 0, 4, 5, 7, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, 3, 0],
    [0, 0, 1, 0, 0, 0, 0, 6, 8],
    [0, 0, 8, 5, 0, 0, 0, 1, 0],
    [0, 9, 0, 0, 0, 0, 4, 0, 0]
]
def solve(bo):
    find = find_empty(bo)
    if not find:  # if find is None or False
        return True
    else:
        row, col = find
    for num in range(1, 10):
        if valid(bo, num, (row, col)):
            bo[row][col] = num
            if solve(bo):
                return True
            bo[row][col] = 0
    return False
def valid(bo, num, pos):
    # Check row
    for i in range(len(bo[0])):
        if bo[pos[0]][i] == num and pos[1] != i:
            return False
    # Check column
    for i in range(len(bo)):
        if bo[i][pos[1]] == num and pos[0] != i:
            return False
    # Check box
    box_x = pos[1] // 3
    box_y = pos[0] // 3
    for i in range(box_y*3, box_y*3 + 3):
        for j in range(box_x*3, box_x*3 + 3):
            if bo[i][j] == num and (i, j) != pos:
                return False
    return True
def print_board(bo):
    for i in range(len(bo)):
        if i % 3 == 0:
            if i == 0:
                print(" ┎─────────────────────────┒")
            else:
                print(" ┠─────────────────────────┨")
        for j in range(len(bo[0])):
            if j % 3 == 0:
                print(" ┃ ", end=" ")
            if j == 8:
                print(bo[i][j], " ┃")
            else:
                print(bo[i][j], end=" ")
    print(" ┖─────────────────────────┚")
def find_empty(bo):
    for i in range(len(bo)):
        for j in range(len(bo[0])):
            if bo[i][j] == 0:
                return i, j  # row, column
    return None
print('\n--------------------------------------\n')
print('× Unsolved Suduku :-')
print_board(board)
print('\n--------------------------------------\n')
t1 = perf_counter()
solve(board)
t2 = perf_counter()
print('× Solved Suduku :-')
print_board(board)
print('\n--------------------------------------\n')
print(f' TIME TAKEN = {round(t2-t1,3)} SECONDS')
print('\n--------------------------------------\n')

Het maakt gebruik van backtracking. Maar is niet door mij gecodeerd, het is Tech With Tim’s. Die lijst bevat de moeilijkste sudoku ter wereld, en door de timingfunctie te implementeren, is de tijd:

===========================
[Finished in 2.838 seconds]
===========================

Maar met een eenvoudige sudoku-puzzel zoals:

board = [
    [7, 8, 0, 4, 0, 0, 1, 2, 0],
    [6, 0, 0, 0, 7, 5, 0, 0, 9],
    [0, 0, 0, 6, 0, 1, 0, 7, 8],
    [0, 0, 7, 0, 4, 0, 2, 6, 0],
    [0, 0, 1, 0, 5, 0, 9, 3, 0],
    [9, 0, 4, 0, 6, 0, 0, 0, 5],
    [0, 7, 0, 3, 0, 0, 0, 1, 2],
    [1, 2, 0, 0, 0, 7, 4, 0, 0],
    [0, 4, 9, 2, 0, 6, 0, 0, 7]
]

Het resultaat is:

===========================
[Finished in 0.011 seconds]
===========================

Behoorlijk snel kan ik zeggen.


Antwoord 6, autoriteit 6%

Er zijn vier stappen om een sudoku-puzzel op te lossen:

  1. Identificeer alle mogelijkheden voor elke cel (van de rij, kolom en doos)
    en probeer een mogelijke matrix te ontwikkelen.
    2. Controleer op dubbel paar, als het bestaat, verwijder dan deze twee waarden uit alle cellen in die rij / kolom / doos, waar het paar ook bestaat
    Als een cel een enkele mogelijkheid heeft, wijs die dan toe:
    voer stap 1 opnieuw uit
  2. Controleer voor elke cel met elke rij, kolom en doos. Als de cel één waarde heeft die niet in de andere mogelijke waarden thuishoort, wijs die waarde dan aan die cel toe.
    voer stap 1 opnieuw uit
  3. Als de sudoku nog steeds niet is opgelost, moeten we de volgende aanname starten,
    Neem de eerste mogelijke waarde aan en wijs deze toe. Voer vervolgens stap 1-3 . uit
    Als het nog steeds niet is opgelost, doe het dan voor de volgende mogelijke waarde en voer het in recursie uit.
  4. Als de sudoku nog steeds niet is opgelost, moeten we de volgende aanname starten,
    Neem de eerste mogelijke waarde aan en wijs deze toe. Voer vervolgens stap 1-3 uit

Als het nog steeds niet is opgelost, doe het dan voor de volgende mogelijke waarde en voer het in recursie uit.

import math
import sys
def is_solved(l):
    for x, i in enumerate(l):
        for y, j in enumerate(i):
            if j == 0:
                # Incomplete
                return None
            for p in range(9):
                if p != x and j == l[p][y]:
                    # Error
                    print('horizontal issue detected!', (x, y))
                    return False
                if p != y and j == l[x][p]:
                    # Error
                    print('vertical issue detected!', (x, y))
                    return False
            i_n, j_n = get_box_start_coordinate(x, y)
            for (i, j) in [(i, j) for p in range(i_n, i_n + 3) for q in range(j_n, j_n + 3)
                           if (p, q) != (x, y) and j == l[p][q]]:
                    # Error
                print('box issue detected!', (x, y))
                return False
    # Solved
    return True
def is_valid(l):
    for x, i in enumerate(l):
        for y, j in enumerate(i):
            if j != 0:
                for p in range(9):
                    if p != x and j == l[p][y]:
                        # Error
                        print('horizontal issue detected!', (x, y))
                        return False
                    if p != y and j == l[x][p]:
                        # Error
                        print('vertical issue detected!', (x, y))
                        return False
                i_n, j_n = get_box_start_coordinate(x, y)
                for (i, j) in [(i, j) for p in range(i_n, i_n + 3) for q in range(j_n, j_n + 3)
                               if (p, q) != (x, y) and j == l[p][q]]:
                        # Error
                    print('box issue detected!', (x, y))
                    return False
    # Solved
    return True
def get_box_start_coordinate(x, y):
    return 3 * int(math.floor(x/3)), 3 * int(math.floor(y/3))
def get_horizontal(x, y, l):
    return [l[x][i] for i in range(9) if l[x][i] > 0]
def get_vertical(x, y, l):
    return [l[i][y] for i in range(9) if l[i][y] > 0]
def get_box(x, y, l):
    existing = []
    i_n, j_n = get_box_start_coordinate(x, y)
    for (i, j) in [(i, j) for i in range(i_n, i_n + 3) for j in range(j_n, j_n + 3)]:
        existing.append(l[i][j]) if l[i][j] > 0 else None
    return existing
def detect_and_simplify_double_pairs(l, pl):
    for (i, j) in [(i, j) for i in range(9) for j in range(9) if len(pl[i][j]) == 2]:
        temp_pair = pl[i][j]
        for p in (p for p in range(j+1, 9) if len(pl[i][p]) == 2 and len(set(pl[i][p]) & set(temp_pair)) == 2):
            for q in (q for q in range(9) if q != j and q != p):
                pl[i][q] = list(set(pl[i][q]) - set(temp_pair))
                if len(pl[i][q]) == 1:
                    l[i][q] = pl[i][q].pop()
                    return True
        for p in (p for p in range(i+1, 9) if len(pl[p][j]) == 2 and len(set(pl[p][j]) & set(temp_pair)) == 2):
            for q in (q for q in range(9) if q != i and p != q):
                pl[q][j] = list(set(pl[q][j]) - set(temp_pair))
                if len(pl[q][j]) == 1:
                    l[q][j] = pl[q][j].pop()
                    return True
        i_n, j_n = get_box_start_coordinate(i, j)
        for (a, b) in [(a, b) for a in range(i_n, i_n+3) for b in range(j_n, j_n+3)
                       if (a, b) != (i, j) and len(pl[a][b]) == 2 and len(set(pl[a][b]) & set(temp_pair)) == 2]:
            for (c, d) in [(c, d) for c in range(i_n, i_n+3) for d in range(j_n, j_n+3)
                           if (c, d) != (a, b) and (c, d) != (i, j)]:
                pl[c][d] = list(set(pl[c][d]) - set(temp_pair))
                if len(pl[c][d]) == 1:
                    l[c][d] = pl[c][d].pop()
                    return True
    return False
def update_unique_horizontal(x, y, l, pl):
    tl = pl[x][y]
    for i in (i for i in range(9) if i != y):
        tl = list(set(tl) - set(pl[x][i]))
    if len(tl) == 1:
        l[x][y] = tl.pop()
        return True
    return False
def update_unique_vertical(x, y, l, pl):
    tl = pl[x][y]
    for i in (i for i in range(9) if i != x):
        tl = list(set(tl) - set(pl[i][y]))
    if len(tl) == 1:
        l[x][y] = tl.pop()
        return True
    return False
def update_unique_box(x, y, l, pl):
    tl = pl[x][y]
    i_n, j_n = get_box_start_coordinate(x, y)
    for (i, j) in [(i, j) for i in range(i_n, i_n+3) for j in range(j_n, j_n+3) if (i, j) != (x, y)]:
        tl = list(set(tl) - set(pl[i][j]))
    if len(tl) == 1:
        l[x][y] = tl.pop()
        return True
    return False
def find_and_place_possibles(l):
    while True:
        pl = populate_possibles(l)
        if pl != False:
            return pl
def populate_possibles(l):
    pl = [[[]for j in i] for i in l]
    for (i, j) in [(i, j) for i in range(9) for j in range(9) if l[i][j] == 0]:
        p = list(set(range(1, 10)) - set(get_horizontal(i, j, l) +
                                         get_vertical(i, j, l) + get_box(i, j, l)))
        if len(p) == 1:
            l[i][j] = p.pop()
            return False
        else:
            pl[i][j] = p
    return pl
def find_and_remove_uniques(l, pl):
    for (i, j) in [(i, j) for i in range(9) for j in range(9) if l[i][j] == 0]:
        if update_unique_horizontal(i, j, l, pl) == True:
            return True
        if update_unique_vertical(i, j, l, pl) == True:
            return True
        if update_unique_box(i, j, l, pl) == True:
            return True
    return False
def try_with_possibilities(l):
    while True:
        improv = False
        pl = find_and_place_possibles(l)
        if detect_and_simplify_double_pairs(
                l, pl) == True:
            continue
        if find_and_remove_uniques(
                l, pl) == True:
            continue
        if improv == False:
            break
    return pl
def get_first_conflict(pl):
    for (x, y) in [(x, y) for x, i in enumerate(pl) for y, j in enumerate(i) if len(j) > 0]:
        return (x, y)
def get_deep_copy(l):
    new_list = [i[:] for i in l]
    return new_list
def run_assumption(l, pl):
    try:
        c = get_first_conflict(pl)
        fl = pl[c[0]
                ][c[1]]
        # print('Assumption Index : ', c)
        # print('Assumption List: ',  fl)
    except:
        return False
    for i in fl:
        new_list = get_deep_copy(l)
        new_list[c[0]][c[1]] = i
        new_pl = try_with_possibilities(new_list)
        is_done = is_solved(new_list)
        if is_done == True:
            l = new_list
            return new_list
        else:
            new_list = run_assumption(new_list, new_pl)
            if new_list != False and is_solved(new_list) == True:
                return new_list
    return False
if __name__ == "__main__":
    l = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 8, 0, 0, 0, 0, 4, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 6, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [2, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 2, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0]
    ]
    # This puzzle copied from Hacked rank test case
    if is_valid(l) == False:
        print("Sorry! Invalid.")
        sys.exit()
    pl = try_with_possibilities(l)
    is_done = is_solved(l)
    if is_done == True:
        for i in l:
            print(i)
        print("Solved!!!")
        sys.exit()
    print("Unable to solve by traditional ways")
    print("Starting assumption based solving")
    new_list = run_assumption(l, pl)
    if new_list != False:
        is_done = is_solved(new_list)
        print('is solved ? - ', is_done)
        for i in new_list:
            print(i)
        if is_done == True:
            print("Solved!!! with assumptions.")
        sys.exit()
    print(l)
    print("Sorry! No Solution. Need to fix the valid function :(")
    sys.exit()

Antwoord 7, autoriteit 3%

Ik ga geen volledige code schrijven, maar ik heb lang geleden een sudoku-oplosser gedaan. Ik ontdekte dat het het niet altijd oploste (wat mensen doen als ze een krant hebben is incompleet!), maar denk nu dat ik weet hoe het moet.

  • Configuratie: voor elk vierkant een set vlaggen voor elk nummer met de toegestane nummers.
  • Doorstrepen: net zoals wanneer mensen in de trein het op papier oplossen, kun je bekende getallen iteratief wegstrepen. Elk vierkantje met slechts één nummer zal een andere doorhaling activeren. Dit zal ofwel resulteren in het oplossen van de hele puzzel, of het zal geen triggers meer hebben. Dit is waar ik de vorige keer tot stilstand kwam.
  • Permutaties: er zijn er maar 9! = 362880 manieren om 9 getallen te rangschikken, gemakkelijk voorberekend op een modern systeem. Alle rijen, kolommen en 3×3 vierkanten moeten een van deze permutaties zijn. Als je eenmaal een aantal cijfers hebt, kun je doen wat je deed met de doorhaling. Voor elke rij/kolom/3×3 mag je 1/9 van de 9 doorstrepen! permutaties als je één getal hebt, 1/(8*9) als je er 2 hebt, enzovoort.
  • Kruispermutaties: je hebt nu een aantal rijen en kolommen met sets van mogelijke permutaties. Maar er is nog een beperking: als je eenmaal een rij hebt ingesteld, worden de kolommen en 3×3’s enorm verkleind in wat ze zouden kunnen zijn. U kunt vanaf hier een boom zoeken om een oplossing te vinden.

Antwoord 8, autoriteit 3%

een korte poging om hetzelfde algoritme te bereiken met behulp van backtracking:

def solve(sudoku):
    #using recursion and backtracking, here we go.
    empties = [(i,j) for i in range(9) for j in range(9) if sudoku[i][j] == 0]
    predict = lambda i, j: set(range(1,10))-set([sudoku[i][j]])-set([sudoku[y+range(1,10,3)[i//3]][x+range(1,10,3)[j//3]] for y in (-1,0,1) for x in (-1,0,1)])-set(sudoku[i])-set(list(zip(*sudoku))[j])
    if len(empties)==0:return True
    gap = next(iter(empties))
    predictions = predict(*gap)
    for i in predictions:
        sudoku[gap[0]][gap[1]] = i
        if solve(sudoku):return True
        sudoku[gap[0]][gap[1]] = 0
    return False

Antwoord 9, autoriteit 3%

Google oftools gebruiken – het volgende genereert een dummy sudoku-array of lost een kandidaat op. De code is waarschijnlijk uitgebreider dan vereist, alle feedback wordt op prijs gesteld.

Het idee is om een beperkingsprogrammeringsprobleem op te lossen dat gepaard gaat met

  1. Lijst van 81 variabelen met gehele getallen tussen 1 en 9.
  2. Allemaal verschillende beperkingen voor rijvector
  3. Allemaal verschillende restricties voor kolomvector
  4. Allemaal verschillende beperkingen voor de submatrices

Bovendien voegen we bij het oplossen van bestaande sudoku extra beperkingen toe aan variabelen waaraan al een waarde is toegewezen.

from ortools.constraint_solver import pywrapcp
import numpy as np
def sudoku_solver(candidate = None):
    solver = pywrapcp.Solver("Sudoku")
    variables = [solver.IntVar(1,9,f"x{i}") for i in range(81)]
    if len(candidate)>0:
        candidate = np.int64(candidate)
        for i in range(81):
            val = candidate[i]
            if val !=0:
                solver.Add(variables[i] == int(val))
    def set_constraints():
        for i in range(9):
            # All columns should be different
            q=[variables[j] for j in list(range(i,81,9))]
            solver.Add(solver.AllDifferent(q))
            #All rows should be different
            q2=[variables[j] for j in list(range(i*9,(i+1)*9))]
            solver.Add(solver.AllDifferent(q2))
            #All values in the sub-matrix should be different
            a = list(range(81))
            sub_blocks = a[3*i:3*(i+9):9] + a[3*i+1:3*(i+9)+1:9] + a[3*i+2:3*(i+9)+2:9]
            q3 = [variables[j] for j in sub_blocks]
            solver.Add(solver.AllDifferent(q3))
    set_constraints()
    db = solver.Phase(variables, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)
    solver.NewSearch(db)    
    results_store =[]
    num_solutions =0
    total_solutions = 5
    while solver.NextSolution() and num_solutions<total_solutions:
        results = [j.Value() for j in variables]
        results_store.append(results)
        num_solutions +=1
    return results_store

Los het volgende sudoku

candidate = np.array([0, 2, 0, 4, 5, 6, 0, 8, 0, 0, 5, 6, 7, 8, 9, 0, 0, 3, 7, 0, 9, 0,
       2, 0, 4, 5, 6, 2, 0, 1, 5, 0, 4, 8, 9, 7, 5, 0, 4, 8, 0, 0, 0, 0,
       0, 3, 1, 0, 6, 4, 5, 9, 7, 0, 0, 0, 5, 0, 7, 8, 3, 1, 2, 8, 0, 7,
       0, 1, 0, 5, 0, 4, 9, 7, 8, 0, 3, 0, 0, 0, 5])
results_store = sudoku_solver(candidate)  

Antwoord 10, autoriteit 3%

Hallo, ik heb geblogd over het schrijven van een geheel nieuwe sudoku-oplosser in Python en ben momenteel bezig met het schrijven van een hele serie over het schrijven van een beperkingsprogrammeeroplosser in Julia (een andere taal van hoog niveau maar sneller)
Je kunt het sudoku-probleem uit een bestand lezen dat gemakkelijker en handiger lijkt dan een gui- of cli-manier. Het algemene idee dat het gebruikt, is constraint-programmering. Ik gebruik de geheel verschillende / unieke beperking, maar ik heb deze zelf gecodeerd in plaats van een beperkingsprogrammeeroplosser te gebruiken.

Als iemand geïnteresseerd is:


Antwoord 11

Dit is een project dat ik enige tijd geleden heb gedaan met Tensorflow en mnist en automatische oplosser. Het solver-algoritme is echter niet goed gedocumenteerd 🙂

https://github.com/Sohrab82/Sudoku-Solver

Other episodes