JAVA数独解题(五):X-wing(矩阵法)

740 阅读9分钟

说明

参考文章:数独高级技巧:X-wing的原理和应用(19年12月4日) 假设同一列中,一个数字n只能有两个空白格可以填写,并且不在同一宫中。同时存在这个数字n在另外一列由相同的情况,这时,如果四个空白格形成一个矩形。则可以排除顶点所在行的其他空白格中的候选值n。 同理衍生到行的情况,这里代码写作Y-wing。

图片

在这里插入图片描述

算法代码

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", "X-wing(如果在同一列,并且不在同一宫中,存在数字n只有两个单元格可以填写;并且不在这两宫的其他列存在相同的情况,则可以排除在这两行中,其他位置的数字n的情况)"),
    Y_WING(YwingCalc.class, "Y-wing", "X-wing对应"),
    ;

    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;

}

XwingCalc

package com.suduku.calc;

import com.suduku.entity.Box;

import java.util.*;
import java.util.stream.Collectors;

/**
 * X-wing(如果在同一列,并且不在同一宫中,存在数字n只有两个单元格可以填写;并且不在这两宫的其他列存在相同的情况,则可以排除在这两行中,其他位置的数字n的情况) <br/>
 * 与X-wing相对应的,横向也是满足的。
 * 将行和列合并起来,形成矩形。就能够排除4个顶点所在的行列的其余空白格的数字。
 * 测试数据:DataConstant.OTHER_X_WING_01
 */
public class XwingCalc extends AbstractCalc {

    @Override
    Box solve() {
        // 遍历数字
        for(Integer n : Box.INIT_LIST) {
            Set<Map.Entry<Integer, List<Box>>> entries = getYMap().entrySet();
            // 遍历区域
            for(Map.Entry<Integer, List<Box>> e1 : entries) {
                // 获取当前区域包含数字n的空白格
                List<Box> b1List = e1.getValue().stream().filter(b -> b.isBlank() && b.getCList().contains(n)).collect(Collectors.toList());
                // 如果只有两个空白格,并且不在同一宫中
                if(b1List.size() == 2 && b1List.get(0).getG() != b1List.get(1).getG()) {
                    // 遍历第二遍
                    for(Map.Entry<Integer, List<Box>> e2 : entries) {
                        if(e2.getKey() > e1.getKey()) {
                            // 获取当前区域包含数字n的空白格
                            List<Box> b2List = e2.getValue().stream().filter(b -> b.isBlank() && b.getCList().contains(n)).collect(Collectors.toList());
                            // 如果只有两个空白格,并且不在同一宫中
                            if(b2List.size() == 2 && b2List.get(0).getG() != b2List.get(1).getG()) {
                                // 判断是否在同一行中
                                if(b1List.get(0).getX() == b2List.get(0).getX() && b1List.get(1).getX() == b2List.get(1).getX()) {
                                    Box box = clearBoxNum(b1List.get(0), b2List.get(0), n);
                                    if(box != null) {
                                        return box;
                                    }
                                    box = clearBoxNum(b1List.get(1), b2List.get(1), n);
                                    if(box != null) {
                                        return box;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 清理数字 <br/>
     *
     * @param b1 同一列的单元格1
     * @param b2 同一列的单元格2
     * @param n 移除的数字
     * @return "com.suduku.entity.Box"
     */
    private Box clearBoxNum(Box b1, Box b2, Integer n) {
        List<Box> clearBoxList = getXList(b1).stream()
                .filter(b -> b.isBlank() && b.getI() != b1.getI() && b.getI() != b2.getI() && b.getCList().contains(n))
                .collect(Collectors.toList());
        for (Box box : clearBoxList) {
            getListener().sendMsg("移除第【%d】行的数字:%d\n", (b1.getX() + 1), n);
            box.getCList().remove(n);
            return box;
        }
        return null;
    }

}

YwingCalc

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;

/**
 * 与X-wing对应
 * 测试数据:SudoUtil.transpose(DataConstant.OTHER_X_WING_01) 转置后的数独
 */
public class YwingCalc extends AbstractCalc {

    @Override
    Box solve() {
        // 遍历数字
        for(Integer n : Box.INIT_LIST) {
            Set<Map.Entry<Integer, List<Box>>> entries = getXMap().entrySet();
            // 遍历区域
            for(Map.Entry<Integer, List<Box>> e1 : entries) {
                // 获取当前区域包含数字n的空白格
                List<Box> b1List = e1.getValue().stream().filter(b -> b.isBlank() && b.getCList().contains(n)).collect(Collectors.toList());
                // 如果只有两个空白格,并且不在同一宫中
                if(b1List.size() == 2 && b1List.get(0).getG() != b1List.get(1).getG()) {
                    // 遍历第二遍
                    for(Map.Entry<Integer, List<Box>> e2 : entries) {
                        if(e2.getKey() > e1.getKey()) {
                            // 获取当前区域包含数字n的空白格
                            List<Box> b2List = e2.getValue().stream().filter(b -> b.isBlank() && b.getCList().contains(n)).collect(Collectors.toList());
                            // 如果只有两个空白格,并且不在同一宫中
                            if(b2List.size() == 2 && b2List.get(0).getG() != b2List.get(1).getG()) {
                                // 判断是否在同一行中
                                if(b1List.get(0).getY() == b2List.get(0).getY() && b1List.get(1).getY() == b2List.get(1).getY()) {
                                    Box box = clearBoxNum(b1List.get(0), b2List.get(0), n);
                                    if(box != null) {
                                        return box;
                                    }
                                    box = clearBoxNum(b1List.get(1), b2List.get(1), n);
                                    if(box != null) {
                                        return box;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 功能描述: 清理数字 <br/>
     *
     * @param b1 同一行的单元格1
     * @param b2 同一行的单元格2
     * @param n 移除的数字
     * @return "com.suduku.entity.Box"
     */
    private Box clearBoxNum(Box b1, Box b2, Integer n) {
        List<Box> clearBoxList = getYList(b1).stream()
                .filter(b -> b.isBlank() && b.getI() != b1.getI() && b.getI() != b2.getI() && b.getCList().contains(n))
                .collect(Collectors.toList());
        for (Box box : clearBoxList) {
            getListener().sendMsg("移除第【%d】列的数字:%d\n", (b1.getX() + 1), n);
            box.getCList().remove(n);
            return box;
        }
        return null;
    }

}

由于没有找到Y-wing的案例,所以通过矩阵转置来产生场景。在SoduUtil中添加转置代码

SudoUtil

    /**
     * 功能描述: 转置 <br/>
     * 
     * @param str 数独字符串
     * @return "java.lang.String"
     */
    public static String transpose(String str) {
        char[] cs = str.toCharArray();
        char[] rs = new char[cs.length];
        for (int i = 0; i < cs.length; i++) {
            // 行列互换
            rs[(i % 9) * 9 + i / 9] = cs[i];
        }
        return new String(rs);
    }

测试数据

在 DataConstant 中添加

public static final String OTHER_X_WING_01 = "000300100500401090001028600090800001008017002010040800004085000300100040002634000";

输出结果

尝试【X-wing	】
移除第【1】行的数字:9
确认位置【行:1,列:1】	值为:【】	候选值为:【4,6,7,8】
{0}(4678     ) 0 (2467     ) 0 (679      ) |  3 (         ) 0 (5679     ) 0 (69       ) |  1 (         ) 0 (2578     ) 0 (457      ) | 
 5 (         ) 0 (23678    ) 0 (367      ) |  4 (         ) 0 (67       ) 1 (         ) |  0 (237      ) 9 (         ) 0 (378      ) | 
 0 (479      ) 0 (347      ) 1 (         ) |  0 (579      ) 2 (         ) 8 (         ) |  6 (         ) 0 (357      ) 0 (3457     ) | 

 0 (2467     ) 9 (         ) 0 (3567     ) |  8 (         ) 0 (56       ) 0 (236      ) |  0 (3457     ) 0 (3567     ) 1 (         ) | 
 0 (46       ) 0 (3456     ) 8 (         ) |  0 (59       ) 1 (         ) 7 (         ) |  0 (3459     ) 0 (356      ) 2 (         ) | 
 0 (267      ) 1 (         ) 0 (3567     ) |  0 (259      ) 4 (         ) 0 (2369     ) |  8 (         ) 0 (3567     ) 0 (35679    ) | 

 0 (1679     ) 0 (67       ) 4 (         ) |  0 (279      ) 8 (         ) 5 (         ) |  0 (2379     ) 0 (12367    ) 0 (3679     ) | 
 3 (         ) 0 (5678     ) 0 (5679     ) |  1 (         ) 0 (79       ) 0 (29       ) |  0 (2579     ) 4 (         ) 0 (56789    ) | 
 0 (1789     ) 0 (57       ) 2 (         ) |  6 (         ) 3 (         ) 4 (         ) |  0 (579      ) 0 (1578     ) 0 (579      ) | 

尝试【X-wing	】
移除第【1】行的数字:9
确认位置【行:1,列:6】	值为:【】	候选值为:【6】
 0 (4678     ) 0 (2467     ) 0 (679      ) |  3 (         ) 0 (5679     ){0}(6        ) |  1 (         ) 0 (2578     ) 0 (457      ) | 
 5 (         ) 0 (23678    ) 0 (367      ) |  4 (         ) 0 (67       ) 1 (         ) |  0 (237      ) 9 (         ) 0 (378      ) | 
 0 (479      ) 0 (347      ) 1 (         ) |  0 (579      ) 2 (         ) 8 (         ) |  6 (         ) 0 (357      ) 0 (3457     ) | 

 0 (2467     ) 9 (         ) 0 (3567     ) |  8 (         ) 0 (56       ) 0 (236      ) |  0 (3457     ) 0 (3567     ) 1 (         ) | 
 0 (46       ) 0 (3456     ) 8 (         ) |  0 (59       ) 1 (         ) 7 (         ) |  0 (3459     ) 0 (356      ) 2 (         ) | 
 0 (267      ) 1 (         ) 0 (3567     ) |  0 (259      ) 4 (         ) 0 (2369     ) |  8 (         ) 0 (3567     ) 0 (35679    ) | 

 0 (1679     ) 0 (67       ) 4 (         ) |  0 (279      ) 8 (         ) 5 (         ) |  0 (2379     ) 0 (12367    ) 0 (3679     ) | 
 3 (         ) 0 (5678     ) 0 (5679     ) |  1 (         ) 0 (79       ) 0 (29       ) |  0 (2579     ) 4 (         ) 0 (56789    ) | 
 0 (1789     ) 0 (57       ) 2 (         ) |  6 (         ) 3 (         ) 4 (         ) |  0 (579      ) 0 (1578     ) 0 (579      ) | 

尝试【X-wing	】
移除第【8】行的数字:9
确认位置【行:8,列:6】	值为:【】	候选值为:【2】
 0 (4678     ) 0 (2467     ) 0 (679      ) |  3 (         ) 0 (5679     ) 0 (6        ) |  1 (         ) 0 (2578     ) 0 (457      ) | 
 5 (         ) 0 (23678    ) 0 (367      ) |  4 (         ) 0 (67       ) 1 (         ) |  0 (237      ) 9 (         ) 0 (378      ) | 
 0 (479      ) 0 (347      ) 1 (         ) |  0 (579      ) 2 (         ) 8 (         ) |  6 (         ) 0 (357      ) 0 (3457     ) | 

 0 (2467     ) 9 (         ) 0 (3567     ) |  8 (         ) 0 (56       ) 0 (236      ) |  0 (3457     ) 0 (3567     ) 1 (         ) | 
 0 (46       ) 0 (3456     ) 8 (         ) |  0 (59       ) 1 (         ) 7 (         ) |  0 (3459     ) 0 (356      ) 2 (         ) | 
 0 (267      ) 1 (         ) 0 (3567     ) |  0 (259      ) 4 (         ) 0 (2369     ) |  8 (         ) 0 (3567     ) 0 (35679    ) | 

 0 (1679     ) 0 (67       ) 4 (         ) |  0 (279      ) 8 (         ) 5 (         ) |  0 (2379     ) 0 (12367    ) 0 (3679     ) | 
 3 (         ) 0 (5678     ) 0 (5679     ) |  1 (         ) 0 (79       ){0}(2        ) |  0 (2579     ) 4 (         ) 0 (56789    ) | 
 0 (1789     ) 0 (57       ) 2 (         ) |  6 (         ) 3 (         ) 4 (         ) |  0 (579      ) 0 (1578     ) 0 (579      ) | 

尝试【X-wing	】
移除第【8】行的数字:9
确认位置【行:8,列:7】	值为:【】	候选值为:【2,5,7】
 0 (4678     ) 0 (2467     ) 0 (679      ) |  3 (         ) 0 (5679     ) 0 (6        ) |  1 (         ) 0 (2578     ) 0 (457      ) | 
 5 (         ) 0 (23678    ) 0 (367      ) |  4 (         ) 0 (67       ) 1 (         ) |  0 (237      ) 9 (         ) 0 (378      ) | 
 0 (479      ) 0 (347      ) 1 (         ) |  0 (579      ) 2 (         ) 8 (         ) |  6 (         ) 0 (357      ) 0 (3457     ) | 

 0 (2467     ) 9 (         ) 0 (3567     ) |  8 (         ) 0 (56       ) 0 (236      ) |  0 (3457     ) 0 (3567     ) 1 (         ) | 
 0 (46       ) 0 (3456     ) 8 (         ) |  0 (59       ) 1 (         ) 7 (         ) |  0 (3459     ) 0 (356      ) 2 (         ) | 
 0 (267      ) 1 (         ) 0 (3567     ) |  0 (259      ) 4 (         ) 0 (2369     ) |  8 (         ) 0 (3567     ) 0 (35679    ) | 

 0 (1679     ) 0 (67       ) 4 (         ) |  0 (279      ) 8 (         ) 5 (         ) |  0 (2379     ) 0 (12367    ) 0 (3679     ) | 
 3 (         ) 0 (5678     ) 0 (5679     ) |  1 (         ) 0 (79       ) 0 (2        ) | {0}(257      ) 4 (         ) 0 (56789    ) | 
 0 (1789     ) 0 (57       ) 2 (         ) |  6 (         ) 3 (         ) 4 (         ) |  0 (579      ) 0 (1578     ) 0 (579      ) | 

尝试【X-wing	】
移除第【8】行的数字:9
确认位置【行:8,列:9】	值为:【】	候选值为:【5,6,7,8】
 0 (4678     ) 0 (2467     ) 0 (679      ) |  3 (         ) 0 (5679     ) 0 (6        ) |  1 (         ) 0 (2578     ) 0 (457      ) | 
 5 (         ) 0 (23678    ) 0 (367      ) |  4 (         ) 0 (67       ) 1 (         ) |  0 (237      ) 9 (         ) 0 (378      ) | 
 0 (479      ) 0 (347      ) 1 (         ) |  0 (579      ) 2 (         ) 8 (         ) |  6 (         ) 0 (357      ) 0 (3457     ) | 

 0 (2467     ) 9 (         ) 0 (3567     ) |  8 (         ) 0 (56       ) 0 (236      ) |  0 (3457     ) 0 (3567     ) 1 (         ) | 
 0 (46       ) 0 (3456     ) 8 (         ) |  0 (59       ) 1 (         ) 7 (         ) |  0 (3459     ) 0 (356      ) 2 (         ) | 
 0 (267      ) 1 (         ) 0 (3567     ) |  0 (259      ) 4 (         ) 0 (2369     ) |  8 (         ) 0 (3567     ) 0 (35679    ) | 

 0 (1679     ) 0 (67       ) 4 (         ) |  0 (279      ) 8 (         ) 5 (         ) |  0 (2379     ) 0 (12367    ) 0 (3679     ) | 
 3 (         ) 0 (5678     ) 0 (5679     ) |  1 (         ) 0 (79       ) 0 (2        ) |  0 (257      ) 4 (         ){0}(5678     ) | 
 0 (1789     ) 0 (57       ) 2 (         ) |  6 (         ) 3 (         ) 4 (         ) |  0 (579      ) 0 (1578     ) 0 (579      ) | 

最终结果

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

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

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

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

总结

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

代码详情

代码地址