JAVA数独解题(十一):数链法(数块法)

345 阅读5分钟

说明

参考文章:数独高手必备:把数串法推广到数块法(20年6月2日) 如果某列中,数字n只有3个空白格B1,B2,B3,并且其中有两个在同一个宫内,另外一个在不同宫内,同时存在另外一个列中,数字n只有两个空白格B4,B5,且不在同一宫内,并且不在B1B2B3宫内。并且满足B1,B2,B3与B4,B5能够形成矩形。则在有两个空白格的宫内与矩形边共同作用的区域,不能有数字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_K(SuLianKuaiCalc.class, "数链数块"),
    SU_LIAN_V(SuLianVerticalCalc.class, "数链垂直"),
    SU_LIAN_T(SuLianTrapezoidCalc.class, "数链梯形"),
    SU_LIAN_J(SuLianDingCalc.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;

}

SuLianKuaiCalc

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只有3个空白格B1,B2,B3,并且其中有两个在同一个宫内,另外一个在不同宫内,
 * 同时存在另外一个列中,数字n只有两个空白格B4,B5,且不在同一宫内,并且不在B1B2B3宫内。
 * 并且满足B1,B2,B3与B4,B5能够形成矩形。
 * 则在有两个空白格的宫内与矩形边共同作用的区域,不能有数字n) <br/>
 *
 * 测试数据:DataConstant.OTHER_SU_LIAN_K_01
 * 测试数据:SudoUtil.transpose(DataConstant.OTHER_SU_LIAN_K_01)
 */
public class SuLianKuaiCalc extends AbstractCalc {

    /**
     * 1. 遍历所有数字
     * 2. 遍历所有行,找出数字n的空白格,并且只有3个还满足 B1B2|B3 或 B1|B2B3的分布条件
     * 3. 再次遍历所有行,找出数字n的空白格,且只有2个,还满足跟第一列不在同一宫,且两个空白格也不在同一宫
     * 4. 校验是否其中有4个点形成矩形
     * 5. 剔除单元格
     */
    @Override
    Box solve() {
        for(Integer n : Box.INIT_LIST) {
            Box clearBox = findAndClear(getYentries(), n, Box::getX);
            if (clearBox != null) {
                return clearBox;
            }
            clearBox = findAndClear(getXentries(), n, Box::getY);
            if (clearBox != null) {
                return clearBox;
            }
        }
        return null;
    }

    /**
     * 功能描述: 查找并清理 <br/>
     *
     * @param entries 遍历的区域,行或列
     * @param n 候选值
     * @param getFun 方法
     * @return "com.suduku.entity.Box"
     */
    private Box findAndClear(Set<Map.Entry<Integer, List<Box>>> entries, Integer n, Function<Box, Integer> getFun) {
        // 遍历所有列,获取数字n所在的列只有3个空白格,并且其中有两个空白格在同一宫内
        for (Map.Entry<Integer, List<Box>> l1Entry : entries) {
            List<Box> b1b2b3List = l1Entry.getValue().stream()
                    .filter(b -> b.isBlank() && b.getCList().contains(n))
                    .collect(Collectors.toList());
            // 判断是否有两个空白格在同一宫内,另外一个空白格不在该宫内
            if(SudoUtil.isTwoBoxAndOne(b1b2b3List)) {
                for (Map.Entry<Integer, List<Box>> l2Entry : entries) {
                    // 再查找出只有两个空白格的列
                    List<Box> b4b5List = l2Entry.getValue().stream()
                            .filter(b -> b.isBlank() && b.getCList().contains(n))
                            .collect(Collectors.toList());
                    if (SudoUtil.isTwoBox(b4b5List) && b1b2b3List.get(0).getG() != b4b5List.get(0).getG()) {
                        // 形成矩形,并清理单元格
                        Box clearBox = findAndClear(b1b2b3List.get(0), b1b2b3List.get(1), b1b2b3List.get(2), b4b5List, n, getFun);
                        if (clearBox != null) {
                            return clearBox;
                        }
                        clearBox = findAndClear(b1b2b3List.get(1), b1b2b3List.get(0), b1b2b3List.get(2), b4b5List, n, getFun);
                        if (clearBox != null) {
                            return clearBox;
                        }
                        clearBox = findAndClear(b1b2b3List.get(2), b1b2b3List.get(0), b1b2b3List.get(1), b4b5List, n, getFun);
                        if (clearBox != null) {
                            return clearBox;
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 发现矩形,并且清理单元格 <br/>
     *
     * @param b1 不在矩形内的点
     * @param b2 矩形第一个端点
     * @param b3 矩形第二个端点
     * @param b4b5List 矩形端点
     * @param n 候选值
     * @return "com.suduku.entity.Box"
     */
    private Box findAndClear(Box b1, Box b2, Box b3, List<Box> b4b5List, Integer n, Function<Box, Integer> getFun) {
        // 判断是否形成矩形
        if(getFun.apply(b2).equals(getFun.apply(b4b5List.get(0))) && getFun.apply(b3).equals(getFun.apply(b4b5List.get(1)))) {
            // 获取宫内数据
            List<Box> clearList = getGList(b1).stream()
                    .filter(b -> b.isBlank()
                            && b.getX() == (b1.getG() == b2.getG() ? getFun.apply(b2) : getFun.apply(b3))
                            && b.getI() != b2.getI() && b.getI() != b3.getI()
                            && b.getCList().contains(n))
                    .collect(Collectors.toList());
            for(Box box : clearList) {
                box.removeCList(n);
                change(box, SudoUtil.getList(b4b5List, b1, b2, b3), n);
                return box;
            }
        }
        return null;
    }

}

SudoUtil

   /**
     * 功能描述: 是否两个单元格在一个宫内,并且另外一个不在该宫内 <br/>
     *
     * @param list 列表
     * @return "boolean"
     */
    public static boolean isTwoBoxAndOne(List<Box> list) {
        // B1B2 | B3
        return list.size() == 3
                && ((list.get(0).getG() == list.get(1).getG() && list.get(0).getG() != list.get(2).getG())
                    // B1 | B2B3
                    || (list.get(0).getG() != list.get(1).getG() && list.get(1).getG() == list.get(2).getG()));
    }

测试数据

在 DataConstant 中添加

public static final String OTHER_SU_LIAN_K_01 = "020089103398001042710032089806290314109346820243018096932800461567124938481963200";

输出结果

尝试【数链数块	】
Box{v=0, i=15,	 x=1, y=6, cList=[5, 6, 7]}
Box{v=0, i=51,	 x=5, y=6, cList=[5, 7]}
Box{v=0, i=3,	 x=0, y=3, cList=[4, 5, 7]}
Box{v=0, i=12,	 x=1, y=3, cList=[5, 6, 7]}
Box{v=0, i=48,	 x=5, y=3, cList=[5, 7]}
清除单元格【Box{v=5, i=13,	 x=1, y=4, cList=[]}】的候选值[7]
 6 (         ) 2 (         ) 0 (45       ) | [0](457      ) 8 (         ) 9 (         ) |  1 (         ) 0 (57       ) 3 (         ) | 
 3 (         ) 9 (         ) 8 (         ) | [0](567      ){5}(         ) 1 (         ) | [0](567      ) 4 (         ) 2 (         ) | 
 7 (         ) 1 (         ) 0 (45       ) |  0 (456      ) 3 (         ) 2 (         ) |  0 (56       ) 8 (         ) 9 (         ) | 

 8 (         ) 0 (57       ) 6 (         ) |  2 (         ) 9 (         ) 0 (57       ) |  3 (         ) 1 (         ) 4 (         ) | 
 1 (         ) 0 (57       ) 9 (         ) |  3 (         ) 4 (         ) 6 (         ) |  8 (         ) 2 (         ) 0 (57       ) | 
 2 (         ) 4 (         ) 3 (         ) | [0](57       ) 1 (         ) 8 (         ) | [0](57       ) 9 (         ) 6 (         ) | 

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

最终结果

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

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

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

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

总结

由于之前编写的代码,没有考虑输出直观,所以重构了输出展示的方式。请先下载最新代码查看

代码详情

代码地址