Go学习——如何用程序解数独?

1、序言

刚开始接触Go语言,学习一些初步用法,带着问题:“如何用程序解数独?”,一起学习吧!

2、背景

数独(Sudoku)是一种逻辑性的数字填空游戏,起源于18世纪的瑞士数学家莱昂哈德·欧拉(Leonhard Euler)所发明的拉丁方阵。数独游戏的基本规则是在一个9x9的网格中填入数字,使得每一行、每一列以及划分的9个3x3的小网格内,1到9的数字都只出现一次。

3、思路分析

step1:声明一个全局9*9数组,需要给出初始值(默认该数组有唯一解,多解后续讨论)

step2:遍历该数组的每一个位置,找出该位置满足以下三个条件的所有情况,

        A、在1-9的数字中,该位置所在的行中未出现的值;

        B、在1-9的数字中,该位置所在的列中未出现的值;

        C、在1-9的数字中,该位置所在的3*3小方格中未出现的值;

step3:如果step2中的此位置出现的可能性只有一种,那么就认为此处的正解值是此值;

step4:如果step2中的此位置出现的可能性有多种,判断这些值每一个出现了多少次,如果已经出现了8次,那么就认为此处的正解值是此值;

step5:综上,如果此次遍历出现了正解值,则继续遍历,否则跳出循环;

step6:每次遍历前,判断是否需要进行求解,否则认为无解或已求出正解。

4、代码实现

package main

import (
	"fmt"
	"strconv"
	"strings"
)

/*	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0},*/

var arr = [9][9]int{
	{4, 0, 0, 6, 0, 7, 0, 0, 0},
	{0, 6, 0, 2, 0, 9, 0, 0, 0},
	{0, 7, 0, 0, 0, 4, 0, 6, 0},
	{1, 0, 0, 0, 0, 2, 0, 5, 0},
	{7, 4, 5, 9, 6, 3, 0, 0, 0},
	{0, 0, 0, 5, 7, 0, 0, 0, 0},
	{2, 5, 7, 3, 1, 8, 6, 4, 9},
	{6, 1, 0, 0, 0, 0, 8, 2, 7},
	{9, 8, 4, 7, 2, 6, 5, 3, 1},
}

var numCount map[int]int
var arrTemp [9][9]int

func main() {
	for !isFinish() {
		updateMap()
		if !sign() {
			fmt.Println("无解!进入测试值阶段……")
			//fmt.Println(arrXJudge)
			// 保存当前arr副本
			saveTemp()
			// 为了找出错误解
			canTest()
			break
		}
		printArr()
	}
	if isFinish() {
		fmt.Println("真解:")
	} else {
		fmt.Println("无解:")
	}
	printArr()

	fmt.Println("验证过程中……")
	validateArr()
}

func validateArr() {
	var count map[int]int
	if count == nil {
		count = make(map[int]int)
	}
	for i := 1; i < 10; i++ {
		count[i] = 0
	}
	for _, k1 := range arr {
		for _, k2 := range k1 {
			if k2 != 0 {
				count[k2]++
			}
		}
	}
	fmt.Println(count)
}

// 将arrOrg的值保存到arrTar
func revertTemp() {
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			arr[i][j] = arrTemp[i][j]
		}
	}
}

// 将arrOrg的值保存到arrTar
func saveTemp() {
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			arrTemp[i][j] = arr[i][j]
		}
	}
}

func canTest() {
	var arrXJudge map[string][]int
	if arrXJudge == nil {
		arrXJudge = make(map[string][]int)
	}
	// 找到所有格子可能性
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if arr[i][j] == 0 {
				ret := getRet(i, j)
				fmt.Println("数独[", i, "][", j, "]处可能值为", ret)
				arrXJudge[strconv.Itoa(i)+","+strconv.Itoa(j)] = ret
			}
		}
	}
	//fmt.Println("当前可能性为:", arrXJudge)
	for k, v := range arrXJudge {
		tar := strings.Split(k, ",")
		x, _ := strconv.Atoi(tar[0])
		y, _ := strconv.Atoi(tar[1])
		for _, t := range v {
			arr[x][y] = t
			for !isFinish() {
				if !sign() {
					break
				}
			}
			if isFinish() {
				fmt.Println("真解为设置:arr[", x, "][", y, "]为", t)
				printArr()
				//revertTemp()
				return
			} else {
				// 找到所有格子可能性
				for i := 0; i < 9; i++ {
					for j := 0; j < 9; j++ {
						if arr[i][j] == 0 {
							judgeTar := getRet(i, j)
							if len(judgeTar) == 0 {
								fmt.Println("错误解为设置:arr[", x, "][", y, "]为", t)
							}
						}
					}
				}
				revertTemp()
			}
		}
	}
}

func updateMap() {
	if numCount == nil {
		numCount = make(map[int]int)
	}
	for i := 1; i < 10; i++ {
		numCount[i] = 0
	}
	for _, k1 := range arr {
		for _, k2 := range k1 {
			if k2 != 0 {
				numCount[k2]++
			}
		}
	}
	fmt.Println(numCount)
}

// 标记每个0位置的数字的可能性
func sign() bool {
	flag := false
	for i, k1 := range arr {
		for j, k2 := range k1 {
			if k2 == 0 {
				ret := getRet(i, j)
				//fmt.Println("数独[", i, "][", j, "]处可能值为", ret)
				if len(ret) == 1 {
					arr[i][j] = ret[0]
					flag = true
				} else {
					for _, v := range ret {
						if numCount[v] == 8 {
							arr[i][j] = v
							flag = true
						}
					}
				}
			}
		}
	}

	var arrXJudge map[string][]int
	// 每一行的唯一值
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if arr[i][j] == 0 {
				ret := arrXJudge[strconv.Itoa(i)+","+strconv.Itoa(j)]
				// v代表可能性一种
				for _, v := range ret {
					onlyFlag := true
					for m := 0; m < 9; m++ {
						if m == j {
							continue
						}
						// tarArr为此行其它空值的可能性
						tarArr := arrXJudge[strconv.Itoa(i)+","+strconv.Itoa(m)]
						if isInArray(tarArr, v) {
							onlyFlag = false
							break
						}
					}
					if onlyFlag {
						arr[i][j] = v
						delete(arrXJudge, strconv.Itoa(i)+","+strconv.Itoa(j))
						flag = true
					}
				}
			}
		}
	}
	// 每一行的唯一值
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			if arr[j][i] == 0 {
				ret := arrXJudge[strconv.Itoa(j)+","+strconv.Itoa(i)]
				// v代表可能性一种
				for _, v := range ret {
					onlyFlag := true
					for m := 0; m < 9; m++ {
						if m == i {
							continue
						}
						// tarArr为此行其它空值的可能性
						tarArr := arrXJudge[strconv.Itoa(m)+","+strconv.Itoa(i)]
						if isInArray(tarArr, v) {
							onlyFlag = false
							break
						}
					}
					if onlyFlag {
						arr[j][i] = v
						delete(arrXJudge, strconv.Itoa(i)+","+strconv.Itoa(j))
						flag = true
					}
				}
			}
		}
	}
	for i := 1; i < 8; i += 3 {
		for j := 1; j < 8; j += 3 {
			var arrJudge map[string][]int
			if arrJudge == nil {
				arrJudge = make(map[string][]int)
			}
			for m := i - 1; m < i+2; m++ {
				for n := j - 1; n < j+2; n++ {
					if arr[m][n] == 0 {
						arrJudge[strconv.Itoa(m)+","+strconv.Itoa(n)] = getRet(m, n)
					}
				}
			}
			//fmt.Println("每个小三格可能性:", arrJudge)
			// 遍历每个小格子,如果其中可能值为唯一值,则设置该位置值为唯一值
			for k, v := range arrJudge {
				// k为赋值位置
				//fmt.Println(k)
				// v为赋值可能性
				//fmt.Println(v)
				// t为可能中的一个,将来要赋值的值
				for _, t := range v {
					onlyFlag := true
					// k2为比较位置
					for k2, v2 := range arrJudge {
						if k == k2 {
							continue
						}
						if isInArray(v2, t) {
							onlyFlag = false
							break
						}
					}
					if onlyFlag {
						tar := strings.Split(k, ",")
						x, _ := strconv.Atoi(tar[0])
						y, _ := strconv.Atoi(tar[1])
						arr[x][y] = t
						flag = true
					}
				}
			}
		}
	}
	return flag
}

func getRet(i int, j int) []int {
	var tar []int
	t1 := getI(i)
	t1 = append(t1, getJ(j)...)
	t1 = append(t1, get33(i, j)...)
	for i := 1; i < 10; i++ {
		isExist := false
		for _, v := range t1 {
			if v == i {
				isExist = true
				break
			}
		}
		if !isExist {
			tar = append(tar, i)
		}
	}
	return tar
}

// 获取一行
func getI(i int) []int {
	var tar []int
	for _, k2 := range arr[i] {
		if k2 != 0 {
			tar = append(tar, k2)
		}
	}
	return tar
}

// 获取一列
func getJ(j int) []int {
	var tar []int
	for _, k2 := range arr {
		if k2[j] != 0 {
			tar = append(tar, k2[j])
		}
	}
	return tar
}

// 获取3*3
func get33(i int, j int) []int {
	var tar []int
	var centerX int
	var centerY int
	if i <= 2 {
		centerX = 1
	} else if i <= 5 {
		centerX = 4
	} else if i <= 8 {
		centerX = 7
	}
	if j <= 2 {
		centerY = 1
	} else if j <= 5 {
		centerY = 4
	} else if j <= 8 {
		centerY = 7
	}
	for i := centerX - 1; i < centerX+2; i++ {
		for j := centerY - 1; j < centerY+2; j++ {
			tar = append(tar, arr[i][j])
		}
	}
	return tar
}

func printArrTemp() {
	for _, k1 := range arrTemp {
		for _, k2 := range k1 {
			fmt.Print(k2)
		}
		fmt.Println()
	}
}

func printArr() {
	for _, k1 := range arr {
		for _, k2 := range k1 {
			fmt.Print(k2)
		}
		fmt.Println()
	}
}

func isFinish() bool {
	for _, k1 := range arr {
		for _, k2 := range k1 {
			if k2 == 0 {
				return false
			}
		}
	}
	return true
}

func isInArray(arr []int, target int) bool {
	if arr == nil || len(arr) == 0 {
		return false
	}
	for _, num := range arr {
		if num == target {
			return true
		}
	}
	return false
}

5、运行结果

真解:
498637215
361259478
572184963
139842756
745963182
826571394
257318649
613495827
984726531
验证过程中……
map[1:9 2:9 3:9 4:9 5:9 6:9 7:9 8:9 9:9]

6、改进方案

针对所有唯一解数独,程序已经可以全部求解,但针对多解情况,目前还需考虑:

目前存在以下问题:

        A、多解意味者每一处情况的可能性都是多个值,如果是2个,还可以用排除法去试,但是大部分多解情况都是多于2个值,该部分其实也可以考虑遍历;

        B、每一次试错都是一个系统开销,需要保存每一次试错结果,将所有错误结果排除;这部分没有想好如何去实现;

        C、每次都是从源代码输入要解的数独,此处需要添加从图片识别,然后提取到文件,然后读取文件,该部分涉及识别图片的准确度,以及对提取到的数据做校验;

7、未完待续

        针对第6部分的改进只是做了一丁点尝试,只能说浅尝辄止,没有尽兴,以后技术水平上来了接着搞!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值