JAVA数独解题(八):数链法(梯形、摩天楼)

288 阅读8分钟

说明

参考文章:掌握这个技巧,才可能成为数独高手(20年1月4日) 如下图所示,如果同一行中,数字n只有两个空白格B1,B2可以填写,并且空白格不在同一宫中。且存在另外一行,也只有两个空白格B3,B4可以填写数字n,也不在同一宫中。有两个空白格所在的列相同(B1与B3,或B2与B4)【因为都是从左往右查找】,另外两个点的宫在同一列中。则形成梯形数链。可以排除下图蓝色区域内的候选数字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, "数对法"),
    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_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;

}

SuLianTrapezoidCalc

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只有两个空白格B1,B2可以填写,并且空白格不在同一宫中。
 * 且存在另外一行,也只有两个空白格B3,B4可以填写数字n,也不在同一宫中。
 * 有两个空白格所在的列相同(B1与B3,或B2与B4)【因为都是从左往右查找】,
 * 另外两个点的宫在同一列中。则形成梯形数链。可以排除下图蓝色区域内的候选数字n。) <br/>
 *
 * 列方向同理。
 *
 * 测试数据:DataConstant.OTHER_SU_LIAN_T_01
 */
public class SuLianTrapezoidCalc extends AbstractCalc {

    /*
     * 1.遍历所有的候选数字n
     * 2.使用数字n遍历所有的行,查找某一行中,只有两个空白格的候选值中有数字n
     * 3.重新遍历所有行,并且当前行的下标大于第一行的下标。继续查找与第一次获得行相同的两个空白格
     * 4.校验是否形成梯形。
     * 4.1 B1和B3的Y坐标一致,并且B2和B4所在的GY坐标一致,而Y坐标不一致。
     * 4.1.1 如果符合要求,则可以排除B2所在宫中,Y坐标与B4相等的空白格候选数字n
     * 4.1.2                 排除B4所在宫中,Y坐标与B2相等的空白格候选数字n
     * 4.1 B2和B4的Y坐标一致,并且B1和B3所在的GY坐标一致,而Y坐标不一致。
     * 4.1.1 如果符合要求,则可以排除B1所在宫中,Y坐标与B3相等的空白格候选数字n
     * 4.1.2                 排除B3所在宫中,Y坐标与B1相等的空白格候选数字n
     *
     * 5.遍历列的逻辑与上述类似
     */
    @Override
    Box solve() {
        // 遍历候选数字
        for(Integer n : Box.INIT_LIST) {
            // 遍历所有的行
            Box clearBox = findAndClear(getXMap().entrySet(), n, Box::getY);
            if(clearBox != null) {
                return clearBox;
            }
            // 遍历所有列
            clearBox = findAndClear(getYMap().entrySet(), n, Box::getX);
            if(clearBox != null) {
                return clearBox;
            }
        }
        return null;
    }

    private Box findAndClear(Set<Map.Entry<Integer, List<Box>>> areaEntries, Integer n, Function<Box, Integer> getFun) {
        for (Map.Entry<Integer, List<Box>> l1Entry : areaEntries) {
            // 获取B1,B2两个空白格
            List<Box> b12List = SudoUtil.findBoxByCList(l1Entry.getValue(), n);
            if(isTwoBox(b12List)) {
                // 查找B3,B4两个空白格
                for(Map.Entry<Integer, List<Box>> l2Entry : areaEntries) {
                    if(l2Entry.getKey() > l1Entry.getKey()) {
                        List<Box> b34List = SudoUtil.findBoxByCList(l2Entry.getValue(), n);
                        if(isTwoBox(b34List)) {
                            // 确定了4个顶点,判断是否形成梯形
                            if(SudoUtil.isTrapezoid(b12List.get(0), b12List.get(1), b34List.get(0), b34List.get(1), getFun)) {
                                // 斜边在右边,排除b12List.get(1)和b34List.get(1) 所在宫中的空白格
                                Box box = clearBox(b12List.get(1), b34List.get(1), n, getFun);
                                if(box != null) {
                                    return box;
                                }
                            }
                            if(SudoUtil.isTrapezoid(b12List.get(1), b12List.get(0), b34List.get(1), b34List.get(0), getFun)) {
                                // 斜边在左边
                                Box box = clearBox(b12List.get(0), b34List.get(0), n, getFun);
                                if(box != null) {
                                    return box;
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 清理空白格 <br/>
     *
     * @param b2 梯形斜边的两个顶点
     * @param b4 梯形斜边的两个顶点
     * @param n 候选数字n
     * @param getFun 方法 代替 getY和 getX
     * @return "com.suduku.entity.Box"
     */
    private Box clearBox(Box b2, Box b4, Integer n, Function<Box, Integer> getFun) {
        List<Box> clearList = findClearList(b2, b4, n, getFun);
        for(Box box : clearList) {
            getListener().sendMsg("清除数字%d\n", n);
            box.removeCList(n);
            return box;
        }
        return null;
    }

    /**
     * 功能描述: 查找可以清楚的空白格 <br/>
     *
     * @param b2 梯形斜边的两个顶点
     * @param b4 梯形斜边的两个顶点
     * @param n 候选数字n
     * @param getFun 方法 代替 getY和 getX
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    private List<Box> findClearList(Box b2, Box b4, Integer n, Function<Box, Integer> getFun) {
        // B2所在宫中,Y坐标等于B4的Y坐标
        List<Box> b2List = getGList(b2).stream()
                .filter(b -> b.isBlank() && getFun.apply(b).equals(getFun.apply(b4)) && b.getCList().contains(n))
                .collect(Collectors.toList());
        List<Box> b4List = getGList(b4).stream()
                .filter(b -> b.isBlank() && getFun.apply(b).equals(getFun.apply(b2)) && b.getCList().contains(n))
                .collect(Collectors.toList());
        b2List.addAll(b4List);
        return b2List;
    }

    /**
     * 功能描述: 判断是否不在同一宫中的两个单元格 <br/>
     *
     * @param bList 待判断单元格列表
     * @return "boolean"
     */
    private boolean isTwoBox(List<Box> bList) {
        return bList.size() == 2 && bList.get(0).getG() != bList.get(1).getG();
    }

}

SudoUtil

    /**
     * 功能描述: 查找单元格列表 <br/>
     *
     * @param list 列表
     * @param n 候选数字
     * @return "java.util.List<com.suduku.entity.Box>"
     */
    public static List<Box> findBoxByCList(List<Box> list, Integer n) {
        return list.stream()
                .filter(b -> b.isBlank() && b.getCList().contains(n))
                .collect(Collectors.toList());
    }

    /**
     * 功能描述: 是否梯形 <br/>
     *
     * b1----b2
     * |      \
     * |       \
     * |        \
     * b3--------b4
     *
     * @param b1 第一条平行边第一个点,与b3形成直角边
     * @param b2 第一条平行边第二个点,与b4形成斜边
     * @param b3 第二条平行边第一个点,与b1形成直角边
     * @param b4 第二条平行边第二个点,与b2形成斜边
     * @return "boolean"
     */
    public static boolean isYTrapezoid(Box b1, Box b2, Box b3, Box b4) {
        return isTrapezoid(b1, b2, b3, b4, true);
    }

    /**
     * 功能描述: 是否梯形 <br/>
     *
     * b1------b3
     * |       |
     * |       |
     * |       b4
     * |
     * b2
     *
     * @param b1 第一条平行边第一个点,与b3形成直角边
     * @param b2 第一条平行边第二个点,与b4形成斜边
     * @param b3 第二条平行边第一个点,与b1形成直角边
     * @param b4 第二条平行边第二个点,与b2形成斜边
     * @return "boolean"
     */
    public static boolean isXTrapezoid(Box b1, Box b2, Box b3, Box b4) {
        return isTrapezoid(b1, b2, b3, b4, false);
    }

    /**
     * 功能描述: 是否梯形 <br/>
     *
     * @param b1 第一条平行边第一个点
     * @param b2 第一条平行边第二个点
     * @param b3 第二条平行边第一个点
     * @param b4 第二条平行边第二个点
     * @param isY 判断形状
     * @return "boolean"
     */
    public static boolean isTrapezoid(Box b1, Box b2, Box b3, Box b4, boolean isY) {
        return b1.getG() != b3.getG()
                && isY ? b1.getY() == b3.getY() && b2.getGy() == b4.getGy() : b1.getX() == b3.getX() && b2.getGx() == b4.getGx();
    }

    /**
     * 功能描述: 是否梯形 <br/>
     *
     * @param b1 第一条平行边第一个点
     * @param b2 第一条平行边第二个点
     * @param b3 第二条平行边第一个点
     * @param b4 第二条平行边第二个点
     * @param getFun 通过方法判断,传递getY 或 getX 进行区分 代替gx和gy
     * @return "boolean"
     */
    public static boolean isTrapezoid(Box b1, Box b2, Box b3, Box b4, Function<Box, Integer> getFun) {
        return b1.getG() != b3.getG() && getFun.apply(b1).equals(getFun.apply(b3)) && (getFun.apply(b2) / 3) == (getFun.apply(b4) / 3);
    }

测试数据

在 DataConstant 中添加

public static final String OTHER_SU_LIAN_T_01 = "000005200060100090850000000400003000071209403000050060200300907003090020000000341";

输出结果

 0 (1379     ) 0 (1349     ) 0 (479      ) |  0 (46789    ) 0 (34678    ) 5 (         ) |  2 (         ) 0 (378      ) 0 (468      ) | 
 0 (37       ) 6 (         ) 0 (247      ) |  1 (         ) 0 (23478    ) 0 (2478     ) |  0 (578      ) 9 (         ) 0 (458      ) | 
 8 (         ) 5 (         ) 0 (2479     ) |  0 (4679     ) 0 (23467    ) 0 (2467     ) |  0 (167      ) 0 (137      ) 0 (46       ) | 

 4 (         ) 0 (29       ) 0 (56       ) |  0 (678      ) 0 (1678     ) 3 (         ) |  0 (1578     ) 0 (1578     ) 0 (29       ) | 
 0 (56       ) 7 (         ) 1 (         ) |  2 (         ) 0 (68       ) 9 (         ) |  4 (         ) 0 (58       ) 3 (         ) | 
 0 (39       ) 0 (2389     ) 0 (89       ) |  0 (47       ) 5 (         ) 0 (147      ) | {0}(17       ) 6 (         ) 0 (29       ) | 

 2 (         ) 0 (148      ) 0 (4568     ) |  3 (         ) 0 (1468     ) 0 (1468     ) |  9 (         ) 0 (58       ) 7 (         ) | 
 0 (157      ) 0 (148      ) 3 (         ) |  0 (4578     ) 9 (         ) 0 (1478     ) |  0 (568      ) 2 (         ) 0 (568      ) | 
 0 (5679     ) 0 (89       ) 0 (56789    ) |  0 (5678     ) 0 (2678     ) 0 (2678     ) |  3 (         ) 4 (         ) 1 (         ) | 
尝试【数对梯形	】
清除数字5
确认位置【行:4,列:3】	值为:【6】	候选值为:【】
 0 (1379     ) 0 (1349     ) 0 (479      ) |  0 (46789    ) 0 (34678    ) 5 (         ) |  2 (         ) 0 (378      ) 0 (468      ) | 
 0 (37       ) 6 (         ) 0 (247      ) |  1 (         ) 0 (23478    ) 0 (2478     ) |  0 (578      ) 9 (         ) 0 (458      ) | 
 8 (         ) 5 (         ) 0 (2479     ) |  0 (4679     ) 0 (23467    ) 0 (2467     ) |  0 (167      ) 0 (137      ) 0 (46       ) | 

 4 (         ) 0 (29       ){6}(         ) |  0 (78       ) 0 (178      ) 3 (         ) |  0 (1578     ) 0 (1578     ) 0 (29       ) | 
 0 (5        ) 7 (         ) 1 (         ) |  2 (         ) 0 (68       ) 9 (         ) |  4 (         ) 0 (58       ) 3 (         ) | 
 0 (39       ) 0 (2389     ) 0 (89       ) |  0 (47       ) 5 (         ) 0 (147      ) |  0 (17       ) 6 (         ) 0 (29       ) | 

 2 (         ) 0 (148      ) 0 (458      ) |  3 (         ) 0 (1468     ) 0 (1468     ) |  9 (         ) 0 (58       ) 7 (         ) | 
 0 (157      ) 0 (148      ) 3 (         ) |  0 (4578     ) 9 (         ) 0 (1478     ) |  0 (568      ) 2 (         ) 0 (568      ) | 
 0 (5679     ) 0 (89       ) 0 (5789     ) |  0 (5678     ) 0 (2678     ) 0 (2678     ) |  3 (         ) 4 (         ) 1 (         ) | 

最终结果

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

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

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

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

总结

通过函数编程,可以进一步封装方法。达到复用性。

代码详情

代码地址