数独(sudoku)是一种填数字的游戏,在一个9*9的九宫格里面推导出剩余的数字,要求每行、每列、每宫(3*3)的数字均包含1~9,且不重复!
另:世界最难数独(题目中的最后一个数独)被江苏扬州一位农民大叔给做出来了,厉害!
从数独的概念我们可以知道,在每次填数字的时候都需要观察行、列、每个宫格的数字不能重复。如果满足这三个条件,那这个数字暂时是正确的,我们接着就填第二个数字,如果出现1-9数字都填完还不正确的话,就只能将上一次的填写删掉,重新来过,这个回溯的过程很像数据库里的事务处理,当失败的时候,就进行回滚(RollBack)

所以从这个回溯的步骤次数来看,扬州那位大叔真的很有耐心,太牛了

class SudoKu():
    def __init__(self,sudo_ku_data):
        if not isinstance(sudo_ku_data,list):
            raise TypeError('sudo_ku_data参数必须是列表,目前输入{}的类型是{}'.format(sudo_ku_data,type(sudo_ku_data)))

        if len(sudo_ku_data) != 9 or len(sudo_ku_data[0]) != 9:
            raise TypeError('sudo_ku_data参数必须是9*9的列表,目前输入是{}*{}的列表'.format(len(sudo_ku_data),len(sudo_ku_data[0])))
        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 = {}

        # 初始化数据
        self._init()

    def _add_row_data(self,row,value):
        '''每行已有数据初始化到self.every_row_data字典中'''
        if row not in self.every_row_data:
            self.every_row_data[row] = set()#如果行索引不存在,就创建一个空集。类似{0: set()} {0: {8, 4}, 1: set()}

        if value in self.every_row_data[row]:
            raise TypeError('行存在重复数字!')

        self.every_row_data[row].add(value)

    def _add_column_data(self,column,value):
        '''每列已有数据初始化到self.every_column_data字典中'''
        if column not in self.every_column_data:
            self.every_column_data[column] = set()

        if value in self.every_column_data[column]:
            raise TypeError('列存在重复数字!')

        self.every_column_data[column].add(value)

    def _get_three_to_three_key(self,row,column):
        '''1~9标识九宫格的每个宫格(3*3)'''
        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):
        '''初始化九宫格的每宫格(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()

        self.every_three_to_three_data[key].add(value)

    def _init(self):
        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)

    def _judge_value_is_legal(self,row,column,value):
        '''判断行、列、宫的数据是否合法'''

        # 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

    def _calculate(self, vacant_position):
        '''计算,开始对数独进行放置值'''
        # 得到当前位置
        row,column = vacant_position
        values = set(range(1,10))#{1, 2, 3, 4, 5, 6, 7, 8, 9}

        # 对当前位置创建一个唯一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):
                # 更新判断数据合法时需要使用到的数据
                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

    def _backtrack(self,current_vacant_position,previous_vacant_position,previous_value):
        '''
        回溯
        :param current_vacant_position: 当前尝试失败的位置
        :param previous_vacant_position: 上一次成功的位置
        :param previous_value:上一次成功的值
        :return:
        '''
        #删除上一次成功的位置的值
        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)

    def get_result(self):
        '''得到计算之后的数独'''
        # 空缺位置的长度(个数)
        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('无效数独')

        # 打印计算之后的数独
        self.show_sudo_ku()
        return self.sudo_ku

    def show_sudo_ku(self):
        for row in self.sudo_ku:
            print(row)

##################################################
# 用来判断最后计算的数独是否合法,和计算没有关系 #
##################################################

def judge_value_is_legal(row,column,value,sudo_ku):
    # column
    for i in range(0,9):
        if row == i:
            continue
        if value == sudo_ku[i][column]:
            return False

    # row
    for i in range(0,9):
        if column == i:
            continue
        if value == sudo_ku[row][i]:
            return False

    # three_to_three
    for i in range(row//3*3,row//3*3+3):
        for j in range(column//3*3,column//3*3+3):
            if i == row and j == column:
                continue
            if value == sudo_ku[i][j]:
                return False

    return True

def judge_sudo_ku_is_legal(sudo_ku):
    for row,row_values in enumerate(sudo_ku):
        for column,value in enumerate(row_values):
            if not judge_value_is_legal(row,column,value,sudo_ku):
                return False
    return True

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_data2 = [
        [3, '', '', '', '', 1, '', '', ''],
        ['', '', 8, 6, '', '', '', 7, ''],
        ['', 4, '', '', '', '', '', 2, ''],
        [4, '', '', 2, '', '', 7, 5, 1],
        [9, '', 1, '', 8, '', 2, '', 6],
        ['', '', 7, 5, '', '', '', 3, 9],
        ['', 7, 9, '', '', 3, '', 1, 2],
        [6, 3, 2, '', 4, 5, 9, 8, 7],
        [8, 1, 4, '', '', 9, 5, 6, 3],
    ]
    sudo_ku_data3 = [
        [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],
    ]
    sudo_ku_data4 = [
        [8,'','', '', '', '', '', '', ''],
        ['', '', 3, 6, '', '', '', '', ''],
        ['', 7, '', '', 9, '', 2, '', ''],
        ['', 5, '', '', '', 7, '', '', ''],
        ['', '', '', '', 4, 5, 7, '', ''],
        ['', '', '', 1, '', '', '', 3, ''],
        ['', '', 1, '', '', '', '', 6, 8],
        ['', '', 8, 5, '', '', '', 1, ''],
        ['', 9, '', '', '', '', 4, '', ''],
    ]
    sudo_ku = SudoKu(sudo_ku_data3).get_result()
    print(judge_sudo_ku_is_legal(sudo_ku))
    
    #sudo_ku2 = SudoKu(sudo_ku_data4).get_result()
    #print(judge_sudo_ku_is_legal(sudo_ku2))

python生成数独程序 python写数独_游戏

 

python生成数独程序 python写数独_游戏_02