简单的数独小程序

昨天和孩子一起做数独游戏,竟然连入门级的第一道题都做不出来。一怒之下,写了一个小程序,帮助我们做一些简单的分析和判断。

数独(Sudoku)的解题技巧

当某一行(row)、某一列(column)、某一宫(box)中已经出现了8个数字,则余下的一格(cell)一定是第9个数字。
例1:

258?14796

? 处一定是 3

例2:

123?48
6
5
7

? 处一定是 9

例3:

1
1
?
1
1

? 处一定是 1

下面就是我做的入门级数独第一题,有兴趣的同学可以试试看:

0	0	0		0	0	1		5	4	0		
2	1	0		4	0	0		0	0	0		
0	4	0		0	3	0		0	0	0		

4	0	0		0	1	0		0	3	2		
0	0	8		0	6	0		7	0	0		
7	9	0		0	4	0		0	0	6		

0	0	0		0	8	0		0	9	0		
0	0	0		0	0	2		0	8	1		
0	8	5		6	0	0		0	0	0

数独有很多解题技巧,我写的程序只包含了最基本的技巧。

程序源代码以及运行效果

完整的代码如下:

package com.company;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Sudoku {
    // output
    private List<Integer> grid = new ArrayList<Integer>();

    // possible values for each cell
    private List<List<Integer>> possibles = new ArrayList<List<Integer>>();

    // just for print different color on changed cells
    private List<Boolean> changed = new ArrayList<Boolean>();

    // index list in the same row/column/box
    // e.g.: for index "5", the index list in the same row is "0" to "8"
    private List<List<Integer>> indexesInRow = new ArrayList<List<Integer>>();
    private List<List<Integer>> indexesInColumn = new ArrayList<List<Integer>>();
    private List<List<Integer>> indexesInBox = new ArrayList<List<Integer>>();

    // print result for each iteration
    private boolean info_level = false;

    // print result for any update
    private boolean debug_level = false;

    // finished or not
    public boolean isDone() {
        return !grid.stream().anyMatch(Objects::isNull);
    }

    // initialize index row list
    private List<Integer> getIndexesInRow(int index) {
        int startingIndex = index / 9 * 9;
        List<Integer> result = Arrays.asList(startingIndex, startingIndex+1, startingIndex+2, startingIndex+3,
                startingIndex+4, startingIndex+5, startingIndex+6, startingIndex+7, startingIndex+8);
        return result;
    }

    // initialize index column list
    private List<Integer> getIndexesInColumn(int index) {
        int startingIndex = index % 9;
        List<Integer> result = Arrays.asList(startingIndex, startingIndex+9, startingIndex+2*9, startingIndex+3*9,
                startingIndex+4*9, startingIndex+5*9, startingIndex+6*9, startingIndex+7*9, startingIndex+8*9);
        return result;
    }

    // initialize index box list
    private List<Integer> getIndexesInBox(int index) {
        int row = index / 9 / 3;
        int column = index / 3 * 3 % 9;
        int startingIndex = row * 9 * 3 + column;
        List<Integer> result = Arrays.asList(startingIndex, startingIndex+1, startingIndex+2, startingIndex+9,
                startingIndex+10, startingIndex+11, startingIndex+18, startingIndex+19, startingIndex+20);
        return result;
    }

    private void updatePossibleFromRow(int index) {
        List<Integer> possible = possibles.get(index);

        indexesInRow.get(index).forEach(e -> {if (e != index && possibles.get(e).size() == 1) possible.removeAll(possibles.get(e));});

        for (int i = 1; i <= 9; i++) {
            int j = i;
            if (indexesInRow.get(index).stream().anyMatch(e -> possibles.get(e).size() == 1 && possibles.get(e).get(0) == j))
                continue;

            long count = indexesInRow.get(index).stream().filter(e -> e != index && possibles.get(e).contains(j)).count();
            if (count == 0) {
                possible.clear();
                possible.add(i);
                break;
            }
        }
    }

    private void updatePossibleFromColumn(int index) {
        List<Integer> possible = possibles.get(index);

        indexesInColumn.get(index).forEach(e -> {if (e != index && possibles.get(e).size() == 1) possible.removeAll(possibles.get(e));});

        for (int i = 1; i <= 9; i++) {
            int j = i;

            if (indexesInColumn.get(index).stream().anyMatch(e -> possibles.get(e).size() == 1 && possibles.get(e).get(0) == j))
                continue;

            long count = indexesInColumn.get(index).stream().filter(e -> e != index && possibles.get(e).contains(j)).count();
            if (count == 0) {
                possible.clear();
                possible.add(i);
                break;
            }
        }
    }

    private void updatePossibleFromBox(int index) {
        List<Integer> possible = possibles.get(index);

        indexesInBox.get(index).forEach(e -> {if (e != index && possibles.get(e).size() == 1) possible.removeAll(possibles.get(e));});

        for (int i = 1; i <= 9; i++) {
            int j = i;

            if (indexesInBox.get(index).stream().anyMatch(e -> possibles.get(e).size() == 1 && possibles.get(e).get(0) == j))
                continue;

            long count = indexesInBox.get(index).stream().filter(e -> e != index && possibles.get(e).contains(j)).count();
            if (count == 0) {
                possible.clear();
                possible.add(i);
                break;
            }
        }
    }

    private void updatePossible(int index) {
        updatePossibleFromRow(index);
        if (debug_level) {
            System.out.println("after updatePossibleFromRow " + index);
            printPossibles();
        }

        updatePossibleFromColumn(index);
        if (debug_level) {
            System.out.println("after updatePossibleFromColumn " + index);
            printPossibles();
        }

        updatePossibleFromBox(index);
        if (debug_level) {
            System.out.println("after updatePossibleFromBox " + index);
            printPossibles();
        }
    }

    private void updateGrid() {
        for (int i = 0; i <= grid.size() - 1; i++) {
            if (grid.get(i) == null && possibles.get(i).size() == 1) {
                changed.set(i, true);
                grid.set(i, possibles.get(i).get(0));
            }
        }
    }

    public void printPossibles() {
        System.out.println("=============== possibles ===============");
        for (int i = 0; i <= possibles.size() -1; i++) {
            if (i > 0 && i % 3 == 0)
                System.out.print("\t");
            if (i > 0 && i % 9 == 0)
                System.out.println();
            if (i > 0 && i % 27 == 0)
                System.out.println();
            System.out.print(String.format("%-27s", possibles.get(i)) + "\t");
        }
        System.out.println();
        System.out.println();
    }

    public void print() {
        System.out.println("=============== grid ===============");
        for (int i = 0; i <= grid.size() -1; i++) {
            if (i > 0 && i % 3 == 0)
                System.out.print("\t");
            if (i > 0 && i % 9 == 0)
                System.out.println();
            if (i > 0 && i % 27 == 0)
                System.out.println();

            int x = grid.get(i) == null ? 0 : grid.get(i);
            String str = changed.get(i) ? "\033[31;4m" + x + "\033[0m" : "" + x;
            System.out.print(str + "\t");
        }
        System.out.println();
        System.out.println();
    }

    public boolean execute() {
        for (int i = 1; i <= 100; i++) {
            if (info_level)
                System.out.println("=============== iteration " + i + " ===============");
            for (int index = 0; index <= 81-1; index++) {
                updatePossible(index);
            }

            updateGrid();

            if (info_level) {
                printPossibles();
                print();
            }

            if (isDone())
                return true;
        }
        return false;
    }

    public void setRunLevel(String level) {
        if ("info_level".equalsIgnoreCase(level)) {
            info_level = true;
            debug_level = false;
        } else if ("debug_level".equalsIgnoreCase(level)) {
            info_level = true;
            debug_level = true;
        }
    }

    public Sudoku(int[] gridArray) {
        for (int i = 0; i < gridArray.length; i++) {
            int e = gridArray[i];
            grid.add(e == 0 ? null : e);
            possibles.add(e == 0 ? Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toList())
                    : Stream.of(e).collect(Collectors.toList()));
            changed.add(false);
            indexesInRow.add(getIndexesInRow(i));
            indexesInColumn.add(getIndexesInColumn(i));
            indexesInBox.add(getIndexesInBox(i));
        }
    }

    public static void main(String[] args) {
        String run_level = null;
        if (args.length > 0)
            run_level = args[0];

        int[] gridArray =
                {
                        0,0,0,0,0,1,5,4,0,
                        2,1,0,4,0,0,0,0,0,
                        0,4,0,0,3,0,0,0,0,
                        4,0,0,0,1,0,0,3,2,
                        0,0,8,0,6,0,7,0,0,
                        7,9,0,0,4,0,0,0,6,
                        0,0,0,0,8,0,0,9,0,
                        0,0,0,0,0,2,0,8,1,
                        0,8,5,6,0,0,0,0,0
                };
        Sudoku sudoku = new Sudoku(gridArray);

        sudoku.setRunLevel(run_level);

        sudoku.print();

        if (sudoku.execute()){
            System.out.println("success!");
            sudoku.print();
        } else {
            System.out.println("failed...");
            sudoku.print();
            sudoku.printPossibles();
        }

    }
}

运行效果如下:

=============== grid ===============
0	0	0		0	0	1		5	4	0		
2	1	0		4	0	0		0	0	0		
0	4	0		0	3	0		0	0	0		

4	0	0		0	1	0		0	3	2		
0	0	8		0	6	0		7	0	0		
7	9	0		0	4	0		0	0	6		

0	0	0		0	8	0		0	9	0		
0	0	0		0	0	2		0	8	1		
0	8	5		6	0	0		0	0	0	

success!
=============== grid ===============
8	6	3		9	2	1		5	4	7		
2	1	9		4	7	5		3	6	8		
5	4	7		8	3	6		1	2	9		

4	5	6		7	1	8		9	3	2		
3	2	8		5	6	9		7	1	4		
7	9	1		2	4	3		8	5	6		

6	3	2		1	8	7		4	9	5		
9	7	4		3	5	2		6	8	1		
1	8	5		6	9	4		2	7	3	


Process finished with exit code 0

程序说明

实际上,该程序的解题思路非常简单:排除掉所有不可能的值,剩下的一个值即为确定的值。

具体算法如下:

创建一个可能值(possible)矩阵,把每个cell的每个可能值都枚举出来。初始化时,根据cell是否有值,分为两种情况:

  • 无值:则possible列表包含从1到9的所有值,即 [1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 有值:则possible列表只包含该确定值,例如 [7]

Possible初始矩阵如下:

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

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

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

遍历每个cell:

  1. 在同一row里查看其它cell,如果有已经确定的值,则从本cell的可能值列表中remove该值。
    例如:第一个cell,初始化时,其可能值列表为 [1, 2, 3, 4, 5, 6, 7, 8, 9] ,从同一row中remove其它cell的确定值之后,变成 [2, 3, 6, 7, 8, 9]

  2. 遍历数字1到9,以 1 为例,如果同一row里的所有其它cell都不可能为 1 ,则可确定本cell值为 1

  3. 把row换成column,重复上述操作。

  4. 把row换成box,重复上述操作。

  5. 遍历下一个cell。如果所有81个cell都遍历完了,则查看是否完成(所有cell都有确定值)。如果还未完成,则开启下一轮遍历。

例如:cell (4, 3),其初始化可能值列表为 [1, 2, 3, 4, 5, 6, 7, 8, 9] 。在第一轮遍历中,通过同一row,排除了 [1, 2, 3, 4] ,通过同一column,排除了 [5, 8] ,通过同一box,排除了 [7, 9] ,所以最终可以确定其值为 6

为了方便看到每一步更新的过程,程序里设置了2个参数:

  • info_level:每次迭代完成后,打印出数独和possibles矩阵
  • debug_level:possibles矩阵有任何更新,都打印出来

以info_level为例,第一次迭代完成后,打印如下:

0	0	0		0	0	1		5	4	0		
2	1	0		4	0	0		0	0	0		
0	4	0		0	3	0		0	0	0		

4	0	6(new)	0	1	0		0	3	2		
0	0	8		0	6	0		7	0	4(new)		
7	9	0		0	4	0		0	0	6		

0	0	0		0	8	0		0	9	0		
0	0	0		0	0	2		0	8	1		
0	8	5		6	0	0		0	0	0	

标为 (new)64 是第一次迭代后确定的值。实际上在运行程序时,会以红色显示有变化(即确定)的值。如下图:
在这里插入图片描述
如果还不太清楚 64 是如何被确定的,可以使用debug_level来打印出计算过程,此处不再赘述。

我测试了几个数独题目,都很快解出来了,但是由于程序只用到了最基本的技巧,有些需要高级技巧才能解决的数独题目,估计还是做不出来的。为了防止死循环,我设置了最大迭代数量为100,超过100就认输了。当然,如果改为判断“如果当前迭代和上次迭代没有差别则认输”会更好一些。

由于时间仓促,以上就是数独小程序的思路和代码的小结,以后有机会再慢慢完善。

代码位置: https://github.com/dukeding/sudoku

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值