Python解武士数独问题

组合数学题如下:
![](https://img-blog.csdnimg.cn/6c922566babc4a87baace8e84078376d.png
在这里插入图片描述
在这里插入图片描述

参考这位博主的的链接:Python解9*9数独
将武士数独问题视为特殊的9*9问题,即按照左上、右上、中间、左下、右下的顺序,在有解的前提下,能够求解出一个有效解。

项目目录结构:
在这里插入图片描述
关键代码:
main.py

import xlrd
import xlsxwriter
from homework1 import SudoKu


def readExcel(filename, row, col):
    '''
    读取excel文件并转为list返回
    :param filename: 文件名
    :param row:
    :param col:
    :return:
    '''
    workbook = xlrd.open_workbook(filename)
    sheetData = workbook.sheet_by_name('Sheet1')
    # print(sheetData)
    ls = []
    for i in range(row, row + 21):
        lt = []
        for j in range(col, col + 21):
            if sheetData.cell(i, j).value == 0.0:
                lt.append('')

            elif sheetData.cell(i, j).value == '':
                lt.append('*')
            else:
                lt.append(int(sheetData.cell(i, j).value))
        ls.append(lt)
    return ls


def showList(ls):
    """
    打印列表ls
    :param ls:列表ls
    :return:
    """
    print('[')
    for row in ls:
        print(row)
    print(']')


def repalceList(ls, row, col):
    """
    从指定的ls列表取出从(row,col)取出一个9*9的子列表先完成数独并更新
    :param ls: 21*21的列表
    :param row: row坐标
    :param col: col坐标
    :return:
    """
    left_top = []
    for i in range(row, row + 9):
        left_top.append(ls[i][col:col + 9])
    # if row == 12 and col == 12:
    #     showList(left_top)
    # 得到计算好的9*9标准数独
    left_top = SudoKu.SudoKu(left_top).get_result()
    # 判断生成的9*9标准数独是否有效
    # print(SudoKu.judge_sudo_ku_is_legal(left_top))
    # 试探出的9*9标准数独有效,则更新21*21列表的对应模块
    if SudoKu.judge_sudo_ku_is_legal(left_top):
        for i in range(row, row + 9):
            ls[i][col:col + 9] = left_top[i - row]


def writeExcel(filename, ls):
    """
        读取excel文件并转为list返回
        :param ls:
        :param filename: 文件名
        :return:
    """
    workbook = xlsxwriter.Workbook(filename)  # 创建工作簿
    worksheet = workbook.add_worksheet("Sheet1")  # 创建子表
    worksheet.activate()  # 激活工作表
    i = 1  # 从第1行开始写入数据
    while i <= len(ls):
        # 每行的第一列的位置
        row = 'A' + str(i)
        lt = ls[i - 1]
        for j in range(len(lt)):
            if lt[j] == '*':
                lt[j] = ''
        worksheet.write_row(row, lt)
        i += 1
    workbook.close()  # 关闭excel文件


if __name__ == '__main__':
    # 从Excel文件读取出21*21的矩阵
    ls = readExcel("data/input1.xlsx", 0, 0)
    print("input:")
    showList(ls)
    # print(showList(ls))
    repalceList(ls, 0, 0)
    repalceList(ls, 0, 12)
    repalceList(ls, 6, 6)
    repalceList(ls, 12, 0)
    repalceList(ls, 12, 12)
    print("output:")
    showList(ls)
    #结果写入Excel
    # writeExcel("data/output2.xlsx", ls)


SudoKu.py

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 = {}

        # 初始化数据
        self._init()

    def _add_row_data(self, row, value):
        '''
        初始化的时候
        添加数据到self.every_row_data中
        :param row:
        :param value:
        :return:
        '''
        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:
        # if not self.every_column_data.__contains__(column):
            self.every_column_data[column] = set()

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

        # if not self.every_column_data.__contains__(column):
        self.every_column_data[column].add(value)

    def _get_three_to_three_key(self, row, column):
        '''
        得到每一个3*3的key
        :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:
        '''
        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):
        '''
        根据传入的数独,初始化数据
        :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)

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

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

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

        return True

    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}')
                # 更新 判断数据合法时 需要使用到的数据
                if self.every_column_data.__contains__(column):
                    self.every_column_data[column].add(value)
                if self.every_row_data.__contains__(row):
                    self.every_row_data[row].add(value)
                key = self._get_three_to_three_key(row, column)
                if self.every_three_to_three_data.__contains__(key):
                    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:
        '''
        # print(f"run backtracking... value is {previous_value},vacant position is {previous_vacant_position}")
        row, column = previous_vacant_position
        # 对上一次成功的值从需要用到的判断的数据中移除
        if self.every_column_data.__contains__(column):
            self.every_column_data[column].remove(previous_value)
        if self.every_row_data.__contains__(row):
            self.every_row_data[row].remove(previous_value)

        key = self._get_three_to_three_key(row, column)
        if self.every_three_to_three_data.__contains__(key):
            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):
        '''
        得到计算之后的数独
        :return:
        '''
        # 空缺位置的长度
        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:
                if len(tried_values) > 0:
                    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')

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

    def show_sudo_ku(self):
        '''
        显示数独
        :return:
        '''
        print('[')
        for row in self.sudo_ku:
            print(row)
        print(']')


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

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):
    '''
    判断接出来的数独矩阵是否合法
    :param sudo_ku: 数独矩阵
    :return: 判断结果
    '''
    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 = [
#         [2, 5, 7, '', '', '', '', '', 9],
#         [1, 9, 8, '', '', 4, '', '', 3],
#         [6, 4, 3, 9, '', 2, '', '', ''],
#         [8, '', '', 3, '', '', '', '', ''],
#         ['', '', '', 4, '', '',  '',7, ''],
#         ['', '', '', '', 2, '', 1, '', 4],
#         ['', '', 2, '', '', 7, '', 5, 8],
#         ['', '', 6, '', '', '', '', 4, ''],
#         [9, '', 5, '', '', '', 2, '', 1],
#     ]
#     # 得到计算好的数独
#     sudo_ku = SudoKu(sudo_ku_data4).get_result()
#
#     # 判断最后生成的数独是否是有效的
#     print(judge_sudo_ku_is_legal(sudo_ku))

Github传送:完整代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值