JAVA数独解题(十):数链法(数串、多宝鱼)

256 阅读4分钟

说明

参考文章:数独高手必备的两个技巧(20年2月14日) 如果数字n在同一行或列中,只有两个空白格,在同一宫中两个空白格,并且其中有两个顶点的行列相同,则另外两个顶点的共同作用空白格,则不能有数字n

图片

在这里插入图片描述 在这里插入图片描述

算法代码

CalcEnum

在 绑定算法 中添加 属性

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, "单宫行列法"),
    SU_DUI(SuDuiCalc.class, "数对法"),
    SU_LIAN_J(SuLianDingCalc.class, "数链多宝鱼"), // 为了测试算法,将引入提前
    X_WING(XwingCalc.class, "X-wing"),
    Y_WING(YwingCalc.class, "Y-wing"),
    XY_WING(XYwingCalc.class, "XY-wing"),
    YX_WING(YXwingCalc.class, "YX-wing"),
    XYZ_WING(XYZwingCalc.class, "XYZ-wing"),
    SU_LIAN_V(SuLianVerticalCalc.class, "数链垂直"),
    SU_LIAN_T(SuLianTrapezoidCalc.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 final Class<? extends AbstractCalc> clazz;
    private final String name;

}

SuLianDingCalc

package com.suduku.calc;

import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;

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

/**
 * 数链-多宝鱼(如果数字n在同一行或列中,只有两个空白格,在同一宫中两个空白格,
 * 并且其中有两个顶点的行列相同,
 * 则另外两个顶点的共同作用空白格,则不能有数字n) <br/>
 *
 * 测试数据:DataConstant.OTHER_SU_LIAN_J_01
 */
public class SuLianDingCalc extends AbstractCalc {

    /**
     * 1. 遍历所有数字
     * 2. 遍历所有宫,查找数字n只有两个空白格B1,B2
     * 3. 遍历所有行或列,查找数字n只有两个空白格B3,B4(不在同宫中)
     * 4. 校验B1B2,B3B4其中,有两个点处于同一列或行
     * 5. 如果处于符合条件,则判断 另外两点的共同作用空白格不能有数字n
     */
    @Override
    Box solve() {
        for(Integer n : Box.INIT_LIST) {
            for (Map.Entry<Integer, List<Box>> gEntry : getGentries()) {
                // 获取数字n出现的空白格
                List<Box> b1b2List = gEntry.getValue().stream()
                        .filter(b -> b.isBlank() && b.getCList().contains(n))
                        .collect(Collectors.toList());
                if(b1b2List.size() == 2) {
                    // 遍历行,获取B3,B4
                    Box clearBox = findAndClear(b1b2List, getXentries(), n, gEntry.getKey(), Box::getX, Box::getY);
                    if(clearBox != null) {
                        return clearBox;
                    }
                    clearBox = findAndClear(b1b2List, getYentries(), n, gEntry.getKey(), Box::getY, Box::getX);
                    if(clearBox != null) {
                        return clearBox;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 查找交叉点,并且清理共同作用空白格 <br/>
     *
     * @param b1b2List B1B2两个空白点
     * @param entries 区域
     * @param n 候选值
     * @param g 当前宫下标
     * @param getXFun 行坐标 区域是列,则互换
     * @param getYFun 纵坐标 区域是列,则互换
     * @return "com.suduku.entity.Box"
     */
    private Box findAndClear(List<Box> b1b2List, Set<Map.Entry<Integer, List<Box>>> entries, Integer n, Integer g, Function<Box, Integer> getXFun, Function<Box, Integer> getYFun) {
        for(Map.Entry<Integer, List<Box>> xEntry : entries) {
            List<Box> b3b4List = xEntry.getValue().stream()
                    .filter(b -> b.isBlank() && b.getG() != g && b.getCList().contains(n))
                    .collect(Collectors.toList());
            if(b3b4List.size() == 2 && SudoUtil.isTwoBox(b3b4List)) {
                // 查找交叉点,并且清理
                Box clearBox = findAndClear(b1b2List.get(0), b1b2List.get(1), b3b4List.get(0), b3b4List.get(1), n, getXFun, getYFun);
                if(clearBox != null) {
                    return clearBox;
                }
                clearBox = findAndClear(b1b2List.get(1), b1b2List.get(0), b3b4List.get(0), b3b4List.get(1), n, getXFun, getYFun);
                if(clearBox != null) {
                    return clearBox;
                }
                clearBox = findAndClear(b1b2List.get(0), b1b2List.get(1), b3b4List.get(1), b3b4List.get(0), n, getXFun, getYFun);
                if(clearBox != null) {
                    return clearBox;
                }
                clearBox = findAndClear(b1b2List.get(1), b1b2List.get(0), b3b4List.get(1), b3b4List.get(0), n, getXFun, getYFun);
                if(clearBox != null) {
                    return clearBox;
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 查找交叉点,并且清理共同作用空白格 <br/>
     *
     * @param b1 同宫中第一个点与B3同行或列
     * @param b2 同宫中第二个点
     * @param b3 同行或列中第一个点,与B1同行或列
     * @param b4 同行或列中第二个点
     * @param n 候选值
     * @param getXFun 行坐标
     * @param getYFun 纵坐标
     * @return "com.suduku.entity.Box"
     */
    private Box findAndClear(Box b1, Box b2, Box b3, Box b4, Integer n, Function<Box, Integer> getXFun, Function<Box, Integer> getYFun) {
        if(getYFun.apply(b1).equals(getYFun.apply(b3))) {
            // 清理共同作用空白格
            Box clearBox = getBoxByXY(getXFun.apply(b2), getYFun.apply(b4));
            if(clearBox.isBlank() && clearBox.getCList().contains(n)) {
                getListener().sendMsg("清除数字%d\n", n);
                clearBox.removeCList(n);
                return clearBox;
            }
        }
        return null;
    }

}

AbstractCalc

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

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

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

测试数据

在 DataConstant 中添加

    public static final String OTHER_SU_LIAN_J_01 = "001705049040390080900400700195674823000819050486532197029100000604900200010243960";

输出结果

尝试【数链多宝鱼	】
清除数字5
确认位置【行:3,列:9】	值为:【2】	候选值为:【】
 0 (28       ) 6 (         ) 1 (         ) |  7 (         ) 0 (28       ) 5 (         ) |  3 (         ) 4 (         ) 9 (         ) | 
 0 (257      ) 4 (         ) 0 (27       ) |  3 (         ) 9 (         ) 1 (         ) |  0 (56       ) 8 (         ) 0 (56       ) | 
 9 (         ) 0 (35       ) 0 (38       ) |  4 (         ) 0 (68       ) 0 (68       ) |  7 (         ) 1 (         ){2}(         ) | 

 1 (         ) 9 (         ) 5 (         ) |  6 (         ) 7 (         ) 4 (         ) |  8 (         ) 2 (         ) 3 (         ) | 
 0 (237      ) 0 (37       ) 0 (237      ) |  8 (         ) 1 (         ) 9 (         ) |  0 (46       ) 5 (         ) 0 (46       ) | 
 4 (         ) 8 (         ) 6 (         ) |  5 (         ) 3 (         ) 2 (         ) |  1 (         ) 9 (         ) 7 (         ) | 

 0 (3578     ) 2 (         ) 9 (         ) |  1 (         ) 0 (568      ) 0 (678      ) |  0 (45       ) 0 (37       ) 0 (458      ) | 
 6 (         ) 0 (357      ) 4 (         ) |  9 (         ) 0 (58       ) 0 (78       ) |  2 (         ) 0 (37       ) 1 (         ) | 
 0 (578      ) 1 (         ) 0 (78       ) |  2 (         ) 4 (         ) 3 (         ) |  9 (         ) 6 (         ) 0 (58       ) | 

最终结果

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

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

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

总结

在写代码的过程中,研究案例,并且思考相关变化。

代码详情

代码地址