说明
参考文章:数独高级技巧:XYZ-wing法(20年5月26日) 如果一个宫内有B1[a,b,c],如果所在行存在B2[a,c],且同宫内不在B1行中存在B3[b,c],则在当前宫内,B1同行的空白格候选值不会存在数字c。 同理如果所在列存在B2[a,c],且同宫内不在B1列中存在B3[b,c], 则在当前宫内,B1同列的空白格候选值不会存在数字c
图片
如下图所示,若第1行第3列中的数只可能是a,b,c,且第1行第4列中的数只可能是a,c,且第3行第3列中的数只可能是b,c,则第1行第1列和第1行第2列不是c。
算法代码
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"),
;
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;
}
XYZwingCalc
package com.suduku.calc;
import com.suduku.entity.Box;
import com.suduku.util.SudoUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* XYZ-wing(如果一个宫内有B1[a,b,c],
* 如果所在行存在B2[a,c],且同宫内不在B1行中存在B3[b,c],
* 则在当前宫内,B1同行的空白格候选值不会存在数字c。
*
* 同理如果所在列存在B2[a,c],且同宫内不在B1列中存在B3[b,c],
* 则在当前宫内,B1同列的空白格候选值不会存在数字c) <br/>
*
* 有待优化
*
*/
public class XYZwingCalc extends AbstractCalc {
@Override
Box solve() {
// 遍历所有宫
Set<Map.Entry<Integer, List<Box>>> entries = getGMap().entrySet();
for (Map.Entry<Integer, List<Box>> entry : entries) {
List<Box> list = entry.getValue();
// 遍历当前宫
for (Box b1 : list) {
// 确定B1单元格
if(b1.isBlank() && b1.getCList().size() == 3) {
// 获取所在行,确定B2单元格(使用JDK8的函数编程,可以进一步抽象)
Box clearBox = findB2AndClear(list, getXList(b1), b1, Box::getX);
if(clearBox != null) {
return clearBox;
}
// 获取所在列,确定B2单元格
clearBox = findB2AndClear(list, getYList(b1), b1, Box::getY);
if(clearBox != null) {
return clearBox;
}
}
}
}
return null;
}
/**
* 功能描述: 查找B2单元格,并且清理数据 <br/>
*
* @param gList 宫列表
* @param areaList 区域列表,行区域,或列区域
* @param b1 B1单元格
* @param getFun 方法,可以是行,或列
* @return "com.suduku.entity.Box"
*/
private Box findB2AndClear(List<Box> gList, List<Box> areaList, Box b1, Function<Box, Integer> getFun) {
for (Box b2 : areaList) {
// 确定是B2单元格
if(isB2Box(b1, b2)) {
// 确定数字b,和数字c
Integer b = SudoUtil.getOther(b1.getCList(), b2.getCList());
// 尝试使用B2的两个数字分别当数字c,确定B3数字
for (Integer c : b2.getCList()) {
Box clearBox = findB3BoxAndClear(gList, b, c, b1, getFun);
if(clearBox != null) {
return clearBox;
}
}
}
}
return null;
}
/**
* 功能描述: B2是空白格,并且与B1不在同一宫中,而且候选值只有两个,却都在B1的候选值中 <br/>
*
* @param b1 b1
* @param b2 b2
* @return "boolean"
*/
private boolean isB2Box(Box b1, Box b2) {
return b2.isBlank() && b2.getG() != b1.getG() && b2.getCList().size() == 2 && b1.getCList().containsAll(b2.getCList());
}
/**
* 功能描述: B3是空白格,并且候选数字只有两个(保证与B1不一致),不在同一行或同一列,包含数字b和c <br/>
*
* @param list 列表
* @param b 数字b
* @param c 数字c
* @param b1 B1单元格
* @param getFun 方法,可以是行,或列
* @return "com.suduku.entity.Box"
*/
private Box findB3BoxAndClear(List<Box> list, Integer b, Integer c, Box b1, Function<Box, Integer> getFun) {
Box b3 = list.stream()
.filter(box -> box.isBlank() && box.getCList().size() == 2
&& !getFun.apply(box).equals(getFun.apply(b1))
&& box.getCList().containsAll(Arrays.asList(b, c)))
.findAny().orElse(null);
if(b3 != null) {
List<Box> clearList = list.stream()
.filter(box -> box.isBlank() && box.getI() != b1.getI()
&& getFun.apply(box).equals(getFun.apply(b1))
&& box.getCList().contains(c))
.collect(Collectors.toList());
for (Box box : clearList) {
getListener().sendMsg("移除数字%d\n", c);
box.removeCList(c);
return box;
}
}
return null;
}
}
SudoUtil
/**
* 功能描述: 获取列表1中,不在列表2中的数字 <br/>
*
* @param l1 列表1
* @param l2 列表2
* @return "java.lang.Integer"
*/
public static Integer getOther(List<Integer> l1, List<Integer> l2) {
List<Integer> c = l1.stream().filter(l -> !l2.contains(l)).collect(Collectors.toList());
if(c.size() == 1) {
return c.get(0);
}
return null;
}
测试数据
在 DataConstant 中添加
public static final String OTHER_XYZ_WING_01 = "020071006709200013610900724070003000000010070006729300291004057568197432437502100";
输出结果
0 (38 ) 2 ( ) 0 (345 ) | {0}(34 ) 7 ( ) 1 ( ) | 0 (589 ) 0 (89 ) 6 ( ) |
7 ( ) 0 (458 ) 9 ( ) | 2 ( ) 0 (4568 ) 0 (568 ) | 0 (58 ) 1 ( ) 3 ( ) |
6 ( ) 1 ( ) 0 (35 ) | 9 ( ) 0 (358 ) 0 (58 ) | 7 ( ) 2 ( ) 4 ( ) |
0 (189 ) 7 ( ) 0 (245 ) | 0 (468 ) 0 (4568 ) 3 ( ) | 0 (2689 ) 0 (4689 ) 0 (1589 ) |
0 (389 ) 0 (458 ) 0 (2345 ) | 0 (468 ) 1 ( ) 0 (568 ) | 0 (2689 ) 7 ( ) 0 (589 ) |
0 (18 ) 0 (458 ) 6 ( ) | 7 ( ) 2 ( ) 9 ( ) | 3 ( ) 0 (48 ) 0 (158 ) |
2 ( ) 9 ( ) 1 ( ) | 0 (368 ) 0 (368 ) 4 ( ) | 0 (68 ) 5 ( ) 7 ( ) |
5 ( ) 6 ( ) 8 ( ) | 1 ( ) 9 ( ) 7 ( ) | 4 ( ) 3 ( ) 2 ( ) |
4 ( ) 3 ( ) 7 ( ) | 5 ( ) 0 (68 ) 2 ( ) | 1 ( ) 0 (689 ) 0 (89 ) |
尝试【XYZ-wing 】
移除数字3
确认位置【行:1,列:1】 值为:【8】 候选值为:【】
{8}( ) 2 ( ) 0 (345 ) | 0 (34 ) 7 ( ) 1 ( ) | 0 (59 ) 0 (9 ) 6 ( ) |
7 ( ) 0 (45 ) 9 ( ) | 2 ( ) 0 (4568 ) 0 (568 ) | 0 (58 ) 1 ( ) 3 ( ) |
6 ( ) 1 ( ) 0 (35 ) | 9 ( ) 0 (358 ) 0 (58 ) | 7 ( ) 2 ( ) 4 ( ) |
0 (19 ) 7 ( ) 0 (245 ) | 0 (468 ) 0 (4568 ) 3 ( ) | 0 (2689 ) 0 (4689 ) 0 (1589 ) |
0 (39 ) 0 (458 ) 0 (2345 ) | 0 (468 ) 1 ( ) 0 (568 ) | 0 (2689 ) 7 ( ) 0 (589 ) |
0 (1 ) 0 (458 ) 6 ( ) | 7 ( ) 2 ( ) 9 ( ) | 3 ( ) 0 (48 ) 0 (158 ) |
2 ( ) 9 ( ) 1 ( ) | 0 (368 ) 0 (368 ) 4 ( ) | 0 (68 ) 5 ( ) 7 ( ) |
5 ( ) 6 ( ) 8 ( ) | 1 ( ) 9 ( ) 7 ( ) | 4 ( ) 3 ( ) 2 ( ) |
4 ( ) 3 ( ) 7 ( ) | 5 ( ) 0 (68 ) 2 ( ) | 1 ( ) 0 (689 ) 0 (89 ) |
最终结果
8 ( ) 2 ( ) 4 ( ) | 3 ( ) 7 ( ) 1 ( ) | 5 ( ) 9 ( ) 6 ( ) |
7 ( ) 5 ( ) 9 ( ) | 2 ( ) 4 ( ) 6 ( ) | 8 ( ) 1 ( ) 3 ( ) |
6 ( ) 1 ( ) 3 ( ) | 9 ( ) 5 ( ) 8 ( ) | 7 ( ) 2 ( ) 4 ( ) |
9 ( ) 7 ( ) 5 ( ) | 4 ( ) 8 ( ) 3 ( ) | 2 ( ) 6 ( ) 1 ( ) |
3 ( ) 4 ( ) 2 ( ) | 6 ( ) 1 ( ) 5 ( ) | 9 ( ) 7 ( ) 8 ( ) |
1 ( ) 8 ( ) 6 ( ) | 7 ( ) 2 ( ) 9 ( ) | 3 ( ) 4 ( ) 5 ( ) |
2 ( ) 9 ( ) 1 ( ) | 8 ( ){3}( ) 4 ( ) | 6 ( ) 5 ( ) 7 ( ) |
5 ( ) 6 ( ) 8 ( ) | 1 ( ) 9 ( ) 7 ( ) | 4 ( ) 3 ( ) 2 ( ) |
4 ( ) 3 ( ) 7 ( ) | 5 ( ) 6 ( ) 2 ( ) | 1 ( ) 8 ( ) 9 ( ) |
============数独解题完成,尝试次数为:59============
总结
代码在编写过程中,会发现有一些是多余的,这时候就可以删除,有一些设计不合理的,就进行改进。能够抽象的就抽象。在不断编写的过程中,不断完善优化。