JAVA数独解题(三):单宫行列法

110 阅读5分钟

说明

单宫行列法:当前宫内,一个候选数字可能出现的所有空白格位置,都在同一行中,那么与其所在行的另外两宫同行中,不能出现该数字,出现则剔除。

图片

在这里插入图片描述 如上图所有,左下角第七宫中,第8行中,候选数字1只能在同一行中,就可以排除第9宫中同一行的有候选数字1的情况。故能确定第8行第7列数字为5,第8行,第8列的数字为2。 图片取自App Sudo Cool

算法代码

GridXYCalc

package com.suduku.calc;

import com.suduku.entity.Box;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 单宫行列法 <br/>
 * 测试数据:DataConstant.XING_02_10
 */
public class GridXYCalc extends AbstractCalc {

    @Override
    Box solve() {
        Set<Map.Entry<Integer, List<Box>>> entries = getGMap().entrySet();
        // 遍历所有宫
        for(Map.Entry<Integer, List<Box>> entry : entries) {
            // 遍历所有候选数字
            for(Integer n : Box.INIT_LIST) {
                // 获取当前宫中候选数字为n的单元格列表
                List<Box> nList = entry.getValue().stream().filter(b -> b.isBlank() && b.getCList().contains(n)).collect(Collectors.toList());
                // 如果存在候选数字n的空白格
                if(!nList.isEmpty()) {
                    // 获取这些单元格的横坐标,判断是否在同一行中
                    Set<Integer> xSet = nList.stream().map(Box::getX).collect(Collectors.toSet());
                    if(xSet.size() == 1) {
                        // 获取该行中,非当前宫的单元格,如果候选数字中存在n,则剔除
                        Integer x = xSet.iterator().next();
                        List<Box> oxList = getXList(x).stream().filter(b -> b.isBlank() && b.getG() != entry.getKey() && b.getCList().contains(n)).collect(Collectors.toList());
                        for(Box box : oxList) {
                            getListener().sendMsg("通过所在【宫:%d,行:%d】\t确定移除数字【%d】\n", entry.getKey() + 1, x + 1, n);
                            box.getCList().remove(n);
                            return box;
                        }
                    }
                    // 获取这些单元格的纵坐标,判断是否在同一列中
                    Set<Integer> ySet = nList.stream().map(Box::getY).collect(Collectors.toSet());
                    if(ySet.size() == 1) {
                        // 获取该列中,非当前宫的单元格,如果候选数字中存在n,则剔除
                        Integer y = ySet.iterator().next();
                        List<Box> oyList = getYList(y).stream().filter(b -> b.isBlank() && b.getG() != entry.getKey() && b.getCList().contains(n)).collect(Collectors.toList());
                        for(Box box : oyList) {
                            getListener().sendMsg("通过所在【宫:%d,列:%d】\t确定移除数字【%d】\n", entry.getKey() + 1, y + 1, n);
                            box.getCList().remove(n);
                            return box;
                        }
                    }
                }
            }
        }
        return null;
    }

}

输出结果

尝试【	单宫行列法	】
通过所在【宫:4,列:1】	确定移除数字【2】
确认位置【行:2,列:1】	值为:【】	候选值为:【1,3,6,7,8】
 9 (         ) 0 (235      ) 0 (356      ) |  1 (         ) 0 (23       ) 0 (236      ) |  7 (         ) 8 (         ) 4 (         ) | 
{0}(13678    ) 0 (238      ) 0 (13678    ) |  4 (         ) 0 (238      ) 0 (236789   ) |  0 (19       ) 0 (139      ) 5 (         ) | 
 0 (1378     ) 0 (38       ) 4 (         ) |  0 (3789     ) 5 (         ) 0 (3789     ) |  6 (         ) 0 (139      ) 2 (         ) | 

 0 (235      ) 6 (         ) 0 (359      ) |  0 (2359     ) 7 (         ) 0 (2359     ) |  8 (         ) 4 (         ) 1 (         ) | 
 0 (2578     ) 1 (         ) 0 (578      ) |  0 (258      ) 4 (         ) 0 (258      ) |  3 (         ) 6 (         ) 9 (         ) | 
 0 (38       ) 4 (         ) 0 (389      ) |  6 (         ) 1 (         ) 0 (389      ) |  2 (         ) 5 (         ) 7 (         ) | 

 4 (         ) 0 (3589     ) 2 (         ) |  0 (3578     ) 0 (38       ) 0 (3578     ) |  0 (159      ) 0 (179      ) 6 (         ) | 
 0 (1568     ) 7 (         ) 0 (1568     ) |  0 (258      ) 9 (         ) 4 (         ) |  0 (15       ) 0 (12       ) 3 (         ) | 
 0 (35       ) 0 (359      ) 0 (359      ) |  0 (2357     ) 6 (         ) 1 (         ) |  4 (         ) 0 (279      ) 8 (         ) | 

绑定算法

CalcEnum

GRID_XY(GridXYCalc.class, "单宫行列法", "单宫行列法()"),

测试数据

DataConstant

    public static final String XING_02_10 = "900100080000400000004050600060070800010000009000600250402000000070090003000001408";

优化内容

AbstractCalc

添加便捷方法

    /**
     * 功能描述: 算法枚举 <br/>
     *
     * @return "com.suduku.calc.enums.CalcEnum"
     */
    public CalcEnum getCalcEnum() {
        return CalcEnum.indexOf(this.getClass());
    }

    /**
     * 功能描述: 方便使用,减少算法实现中的获取变量太长的写法 <br/>
     *
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getBoxList() {
        return this.sudo.getBoxList();
    }

    /**
     * 功能描述: 方便使用 <br/>
     *
     * @return "com.suduku.listener.SudoListener"
     */
    protected SudoListener getListener() {
        return this.sudo.getListener();
    }

    /**
     * 功能描述: 方便使用 <br/>
     *
     * @return "java.util.Map<java.lang.Integer,java.util.List<com.suduku.entity.Box>>"
     */
    protected Map<Integer, List<Box>> getXMap() {
        return this.sudo.getXMap();
    }

    /**
     * 功能描述: 方便使用 <br/>
     *
     * @return "java.util.Map<java.lang.Integer,java.util.List<com.suduku.entity.Box>>"
     */
    protected Map<Integer, List<Box>> getYMap() {
        return this.sudo.getYMap();
    }

    /**
     * 功能描述: 方便使用 <br/>
     *
     * @return "java.util.Map<java.lang.Integer,java.util.List<com.suduku.entity.Box>>"
     */
    protected Map<Integer, List<Box>> getGMap() {
        return this.sudo.getGMap();
    }

    /**
     * 功能描述: 方便使用,获取当前单元格所在行的单元格集合 <br/>
     *
     * @param box 单元格
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getXList(Box box) {
        return getXMap().get(box.getX());
    }

    /**
     * 功能描述: 方便使用,获取当前单元格所在行的单元格集合 <br/>
     *
     * @param x 行坐标
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getXList(Integer x) {
        return getXMap().get(x);
    }

    /**
     * 功能描述: 方便使用,获取当前单元格所在列的单元格集合 <br/>
     *
     * @param box 单元格
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getYList(Box box) {
        return getYMap().get(box.getY());
    }

    /**
     * 功能描述: 方便使用,获取当前单元格所在列的单元格集合 <br/>
     *
     * @param y 列坐标
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getYList(Integer y) {
        return getYMap().get(y);
    }

    /**
     * 功能描述: 方便使用,获取当前单元格所在宫的单元格集合 <br/>
     *
     * @param box 单元格
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getGList(Box box) {
        return getGMap().get(box.getG());
    }

    /**
     * 功能描述: 方便使用,获取当前单元格所在宫的单元格集合 <br/>
     *
     * @param g 宫坐标
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    protected List<Box> getGList(Integer g) {
        return getGMap().get(g);
    }

CalcEnum

优化indexOf获取方法

package com.suduku.calc.enums;

import com.suduku.calc.*;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

/**
 * 功能描述: 算法枚举 <br/>
 *
 */
@Getter
@AllArgsConstructor
public enum CalcEnum {

    /***/
    ONLY_NUM(OnlyNumCalc.class, "唯余法", "唯一余数法(当前单元格中,候选数字只有一个)"),
    ONLY_BOX(OnlyBoxCalc.class, "摒除法", "摒除法(数字在所在行或列或宫中,只有一个空格能够填写,则确定是唯一数字)"),
    GRID_XY(GridXYCalc.class, "单宫行列法", "单宫行列法()"),
    ;

    private static final Map<Class<? extends AbstractCalc>, CalcEnum> CE_MAP = new HashMap<>(CalcEnum.values().length);

    static {
        for(CalcEnum ce : CalcEnum.values()) {
            CE_MAP.put(ce.getClazz(), ce);
        }
    }

    /**
     * 功能描述: 通过类,获取枚举 <br/>
     *
     * @param clazz 类
     * @return "com.suduku.calc.enums.CalcEnum"
     */
    public static CalcEnum indexOf(Class<? extends AbstractCalc> clazz) {
        return CE_MAP.get(clazz);
    }

    private Class<? extends AbstractCalc> clazz;
    private String name;
    private String msg;

}

SudoPrintImpl

输出优化,在未确定数字的情况下,输出提示空,而不是0

	@Override
    public void change(List<Box> list, Box b) {
        sendMsg("确认位置【行:%d,列:%d】\t值为:【%s】\t候选值为:【%s】\n",
                b.getX() + 1, b.getY() + 1, b.getV() == 0 ? "" : b.getV(),
                b.getCList().stream().map(String::valueOf).collect(Collectors.joining(",")));
        SudoUtil.print(list, b);
    }

总结

通过现有的算法,可以解出该数独。 最终结果:

尝试【	唯余法	】
确认位置【行:9,列:8】	值为:【7】	候选值为:【】
 9 (         ) 5 (         ) 6 (         ) |  1 (         ) 2 (         ) 3 (         ) |  7 (         ) 8 (         ) 4 (         ) | 
 1 (         ) 2 (         ) 7 (         ) |  4 (         ) 8 (         ) 6 (         ) |  9 (         ) 3 (         ) 5 (         ) | 
 8 (         ) 3 (         ) 4 (         ) |  9 (         ) 5 (         ) 7 (         ) |  6 (         ) 1 (         ) 2 (         ) | 

 2 (         ) 6 (         ) 5 (         ) |  3 (         ) 7 (         ) 9 (         ) |  8 (         ) 4 (         ) 1 (         ) | 
 7 (         ) 1 (         ) 8 (         ) |  5 (         ) 4 (         ) 2 (         ) |  3 (         ) 6 (         ) 9 (         ) | 
 3 (         ) 4 (         ) 9 (         ) |  6 (         ) 1 (         ) 8 (         ) |  2 (         ) 5 (         ) 7 (         ) | 

 4 (         ) 8 (         ) 2 (         ) |  7 (         ) 3 (         ) 5 (         ) |  1 (         ) 9 (         ) 6 (         ) | 
 6 (         ) 7 (         ) 1 (         ) |  8 (         ) 9 (         ) 4 (         ) |  5 (         ) 2 (         ) 3 (         ) | 
 5 (         ) 9 (         ) 3 (         ) |  2 (         ) 6 (         ) 1 (         ) |  4 (         ){7}(         ) 8 (         ) | 

============数独解题完成,尝试次数为:69============

代码详情

代码地址