Friends Sudoku will be cut? Does python solve Sudoku in seconds?

insert image description here

A few days ago, I was found playing Douzhuzhu with the neighbor next door, and the cards were confiscated. Doudizhu couldn’t be fought anymore, but I still wanted to play with the neighbor. If you still want to fight landlords, poke: while Lao Wang is away, fight landlords with the next-door neighbor, Bibi big and small

Thinking of breaking my head finally made me think of a game, Sudoku! What is Sudoku? Sudoku is a game that allows me to play with my next-door neighbor while Pharaoh is away!

insert image description here

Sudoku rules

1. The numbers 1-9 can only appear once in each line.

2. Numbers 1-9 can only appear once in each column.

3. The numbers 1-9 can only appear once in each 3x3 house. 3x3 intrauterine for A1-C3,A4-C6,A7-C9,D1-F3,D4-F6,D7-F9...

Sudoku Question Example

insert image description here

general idea

1. We use a two-dimensional list to store Sudoku, and we use the '' empty character to occupy the place where there is no value. (Two-dimensional array)

2. Get the existing data of each 3*3 palace, each row, and each column, and then store it. 3. Get all the vacancies, then traverse the vacancies, try to place the data, and then make a judgment. If the conditions are met, continue to place the next one. By analogy, if there is a situation that does not meet the conditions on the way, it will backtrack, return to the last situation that met the conditions, and make another attempt.

insert image description here

demo environment

  • Operating system: windows10
  • python version: python 3.7
  • Code editor: pycharm 2018.2

specific code

1. First we create a class SudoKu. Write the constructor.

class SudoKu():
    def __init__(self,sudo_ku_data):
        # 判断传入的数独是否满足格式
        if not isinstance(sudo_ku_data,list):
            raise TypeError(f'sudo_ku_data params must a list, but {sudo_ku_data} is a {type(sudo_ku_data)}')

        if len(sudo_ku_data) != 9 or len(sudo_ku_data[0]) != 9:
            raise TypeError(f'sudo_ku_data params must a 9*9 list, but {sudo_ku_data} is a {len(sudo_ku_data)}*{len(sudo_ku_data[0])} list')

        self.sudo_ku = sudo_ku_data
        # 存放每一行已有的数据
        self.every_row_data = {}
        # 每一列已有的数字
        self.every_column_data = {}
        # 每一个3*3宫内有的数字
        self.every_three_to_three_data = {}
        # 每一个空缺的位置
        self.vacant_position = []
        # 每一个空缺位置尝试了的数字
        self.every_vacant_position_tried_values = {}

2. Write and add each row, each column, and each palace method, which is convenient for us to call later

def _add_row_data(self,row,value):
    '''
    添加数据到self.every_row_data中,即对每一行已有的数据进行添加
    :param row:
    :param value:
    :return:
    '''
    # 如果当前行不存在,就以当前行为key,初始化值为set()(空的集合)
    if row not in self.every_row_data:
        self.every_row_data[row] = set()

    # 如果这个值已经出现过在这一行了,说明传入的不是一个正确的数独
    if value in self.every_row_data[row]:
        raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

    self.every_row_data[row].add(value)

def _add_column_data(self,column,value):
    '''
    添加数据到self.every_column_data中,上面的函数思路一样
    :param column:
    :param value:
    :return:
    '''
    if column not in self.every_column_data:
        self.every_column_data[column] = set()

    if value in self.every_column_data[column]:
        raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

    self.every_column_data[column].add(value)

def _get_three_to_three_key(self,row,column):
    '''
    得到该位置在哪一个3*3的宫内
    :param row:
    :param column:
    :return:
    '''
    if row in [0,1,2]:
        if column in [0,1,2]:
            key = 1
        elif column in [3,4,5]:
            key = 2
        else:
            key = 3
    elif row in [3,4,5]:
        if column in [0,1,2]:
            key = 4
        elif column in [3,4,5]:
            key = 5
        else:
            key = 6
    else:
        if column in [0,1,2]:
            key = 7
        elif column in [3,4,5]:
            key = 8
        else:
            key = 9

    return key

def _add_three_to_three_data(self,row,column,value):
    '''
    添加数据到self.every_three_to_three_data中
    :param row:
    :param column:
    :param value:
    :return:
    '''
    # 首先得到在哪一个3*3的宫内
    key = self._get_three_to_three_key(row,column)

    # 然后也和上面添加行,列的思路一样
    if key not in self.every_three_to_three_data:
        self.every_three_to_three_data[key] = set()

    if value in self.every_three_to_three_data[key]:
        raise TypeError(f'params {self.sudo_ku} is a invalid SudoKu')

    self.every_three_to_three_data[key].add(value)

3. Traverse Sudoku and initialize each data

def _init(self):
    '''
    根据传入的数独,初始化数据
    :return:
    '''
    for row,row_datas in enumerate(self.sudo_ku):
        for column,value in enumerate(row_datas):
            if value == '':
                # 添加空缺位置
                self.vacant_position.append( (row,column) )
            else:
                # 添加行数据
                self._add_row_data(row,value)
                # 添加列数据
                self._add_column_data(column,value)
                # 添加宫数据
                self._add_three_to_three_data(row,column,value)

4. Write a function to judge whether the value of a certain position is legal

def _judge_value_is_legal(self,row,column,value):
    '''
    判断方放置的数据是否合法
    :param row:
    :param column:
    :param value:
    :return:
    '''

    # value是否存在这一行数据中
    if value in self.every_row_data[row]:
        return False
    # value是否存在这一列数据中
    if value in self.every_column_data[column]:
        return False

    # value是否存在这个3*3的宫内
    key = self._get_three_to_three_key(row,column)
    if value in self.every_three_to_three_data[key]:
        return False

    return True

5. Write a calculation function, cycle the available data at the current position, and determine whether this value can be placed

def _calculate(self, vacant_position):
    '''
    计算,开始对数独进行放置值
    :param vacant_position:
    :return:
    '''
    # 得到当前位置
    row,column = vacant_position
    values = set(range(1,10))

    # 对当前为位置创建一个唯一key,用来存放当前位置已经尝试了的数据
    key = str(row) + str(column)
    # 如果这个key存在,就对values进行取差集,因为两个都是集合(set),直接使用-就行了
    if key in self.every_vacant_position_tried_values:
        values = values - self.every_vacant_position_tried_values[key]
    # 如果这个key不存在,就创建一个空的集合
    else:
        self.every_vacant_position_tried_values[key] = set()

    for value in values:
        # 对当前数据添加到当前位置尝试过的的数据中
        self.every_vacant_position_tried_values[key].add(value)
        # 如果当前value合法,可以放置
        if self._judge_value_is_legal(row,column,value):
            print(f'set {vacant_position} value is {value}')
            # 更新 判断数据合法时 需要使用到的数据
            self.every_column_data[column].add(value)
            self.every_row_data[row].add(value)
            key = self._get_three_to_three_key(row,column)
            self.every_three_to_three_data[key].add(value)

            # 修改这个位置的值为value
            self.sudo_ku[row][column] = value
            # 返回True 和填充的 value
            return True,value

    return False,None

6. If there is no value that can be placed in the current position, then backtrack, return to the last successful position, and re-take the value, so we write a backtracking function

def _backtrack(self,current_vacant_position,previous_vacant_position,previous_value):
    '''
    回溯
    :param current_vacant_position: 当前尝试失败的位置
    :param previous_vacant_position: 上一次成功的位置
    :param previous_value:上一次成功的值
    :return:
    '''
    print(f"run backtracking... value is {previous_value},vacant position is {previous_vacant_position}")
    row,column = previous_vacant_position
    # 对上一次成功的值从需要用到的判断的数据中移除
    self.every_column_data[column].remove(previous_value)
    self.every_row_data[row].remove(previous_value)

    key = self._get_three_to_three_key(row,column)
    self.every_three_to_three_data[key].remove(previous_value)

    # 并且上一次改变的的值变回去
    self.sudo_ku[row][column] = ''

    # 对当前尝试失败的位置已经城市失败的的值进行删除,因为回溯了,所以下一次进来需要重新判断值
    current_row,current_column = current_vacant_position
    key = str(current_row) + str(current_column)
    self.every_vacant_position_tried_values.pop(key)

7. So far, all our functional functions have been written, and then we write a function that starts to loop through all the vacancies. Then do the calculation.

def get_result(self):
    '''
    得到计算之后的数独
    :return:
    '''

    # 首先初始化一下数据
    self._init()

    # 空缺位置的长度
    length = len(self.vacant_position)
    # 空缺位置的下标
    index = 0

    # 存放已经尝试了的数据
    tried_values = []
    # 如果index小于length,说明还没有计算完
    while index < length:
        # 得到一个空缺位置
        vacant_position = self.vacant_position[index]

        # 计入计算函数,返回是否成功,如果成功,value为成功 的值,如果失败,value为None
        is_success,value = self._calculate(vacant_position)
        # 如果成功,将value放在tried_values列表里面,因为列表是有序的.
        # index+1 对下一个位置进行尝试
        if is_success:
            tried_values.append(value)
            index += 1
        # 失败,进行回溯,并且index-1,返回上一次的空缺位置,我们需要传入当前失败的位置 和 上一次成功的位置和值
        else:
            self._backtrack(vacant_position,self.vacant_position[index-1],tried_values.pop())
            index -= 1

        # 如果index<0 了 说明这个数独是无效的
        if index < 0:
            raise ValueError(f'{self.sudo_ku} is a invalid sudo ku')

    # 返回计算之后的数独
    return self.sudo_ku

Show results

call. . . Finally finished the code, then we can "start harvesting"

if __name__ == '__main__':
    sudo_ku_data = [
        [5,3,'','',7,'','','',''],
        [6,'','',1,9,5,'','',''],
        ['',9,8,'','','','',6,''],
        [8,'','','',6,'','','',3],
        [4,'','',8,'',3,'','',1],
        [7,'','','',2,'','','',6],
        ['',6,'','','','',2,8,''],
        ['','','',4,1,9,'','',5],
        ['','','','',8,'','',7,9],
    ]

    # 得到计算好的数独
    sudo_ku = SudoKu(sudo_ku_data).get_result()
    print(sudo_ku)

################
#   结果显示    #
################
[5, 3, 4, 6, 7, 8, 9, 1, 2]
[6, 7, 2, 1, 9, 5, 3, 4, 8]
[1, 9, 8, 3, 4, 2, 5, 6, 7]
[8, 5, 9, 7, 6, 1, 4, 2, 3]
[4, 2, 6, 8, 5, 3, 7, 9, 1]
[7, 1, 3, 9, 2, 4, 8, 5, 6]
[9, 6, 1, 5, 3, 7, 2, 8, 4]
[2, 8, 7, 4, 1, 9, 6, 3, 5]
[3, 4, 5, 2, 8, 6, 1, 7, 9]

This effect is perfect, we are testing a rare Sudoku.

Enter Sudoku as:

[
    [8, '', '', '', '', '', '', '', 4],
    ['', 2, '', '', '', '', '', 7, ''],
    ['', '', 9, 1, '', 6, 5, '', ''],
    ['', '', 6, 2, '', 8, 9, '', ''],
    ['', 9, '', '', 3, '', '', 4, ''],
    ['', '', 2, 4, '', 7, 8, '', ''],
    ['', '', 7, 9, '', 5, 6, '', ''],
    ['', 8, '', '', '', '', '', 2, ''],
    [6, '', '', '', '', '', '', '', 9],
]

################
#   结果显示    #
################
[8, 6, 1, 5, 7, 2, 3, 9, 4]
[5, 2, 4, 3, 8, 9, 1, 7, 6]
[3, 7, 9, 1, 4, 6, 5, 8, 2]
[4, 3, 6, 2, 5, 8, 9, 1, 7]
[7, 9, 8, 6, 3, 1, 2, 4, 5]
[1, 5, 2, 4, 9, 7, 8, 6, 3]
[2, 4, 7, 9, 1, 5, 6, 3, 8]
[9, 8, 5, 7, 6, 3, 4, 2, 1]
[6, 1, 3, 8, 2, 4, 7, 5, 9]

Hahahaha, who will be able to solve Sudoku with me in the future? swell.jpg

The code has all been uploaded to Github: https://github.com/MiracleYoung/You-are-Pythonista/tree/master/PythonExercise/App/solveSudoku/xujin

For more fun and interesting Python, please pay attention to the " Python Column "

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324144074&siteId=291194637