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部分的改进只是做了一丁点尝试,只能说浅尝辄止,没有尽兴,以后技术水平上来了接着搞!