说明
参考文章:数独技巧XY-wing详细介绍(19年11月26日) 如果同一行内只能填写两个数字的单元格(B1[x,y],B2[y,z];并且在B1所在宫内数字y也只有两个单元格B1[x,y],B3[y,z]);且B3中的另一个数字z在B2中出现。则可以剔除B3所在行和B2所在宫交汇区域的数字z
图片
算法代码
CalcEnum
在 绑定算法 中添加 属性,为了保证能够使用到XYWing的解题方法,将XYwing算法注册到Xwing之前。
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, "数对法", "数对法(一个区域内,两个数字只能在两个单元格内,则形成数对,其他值不能填入)"),
XY_WING(XYwingCalc.class, "XY-wing", "XY-wing(如果同一行内只能填写两个数字的单元格(B1[x,y],B2[y,z];并且在B1所在宫内数字y也只有两个单元格B1[x,y],B3[y,z]);且B3中的另一个数字z在B2中出现,则可以剔除B3所在行和B2所在宫交汇区域的数字z)"),
YX_WING(YXwingCalc.class, "YX-wing", "XY-wing对应"),
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;
}
XYwingCalc
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.stream.Collectors;
/**
* XY-wing(如果同一行内只能填写两个数字的单元格(B1[x,y],B2[y,z];
* 并且在B1所在宫内数字y也只有两个单元格B1[x,y],B3[y,z]);
* 且B3中的另一个数字z在B2中出现,
* 则可以剔除B3所在行和B2所在宫交汇区域的数字z) <br/>
* 测试数据:DataConstant.OTHER_XY_WING_01
*/
public class XYwingCalc extends AbstractCalc {
@Override
Box solve() {
Set<Map.Entry<Integer, List<Box>>> entries = getXMap().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() == 2) {
// 遍历B2单元格
for (Box b2 : list) {
// B2不能与B1在同一宫内,并且也只有两个候选数字,并且两个候选数字与B1不能完全相同
if(b2.isBlank() && b2.getG() != b1.getG() && b2.getCList().size() == 2 && !SudoUtil.isEqTwoList(b1.getCList(), b2.getCList())) {
// 确定数字x,为B1和B2中相同的数字
Integer x = SudoUtil.getEqContent(b1.getCList(), b2.getCList());
if(x != null) {
// 从B1中获取数字y,从B2中获取数字z
Integer y = SudoUtil.getOther(b1.getCList(), x);
Integer z = SudoUtil.getOther(b2.getCList(), x);
// 从B1所在宫内,获取根据数字y和z获取单元格B3,B3不能与B1在同一行
List<Box> b3List = getGList(b1).stream()
.filter(b -> b.isBlank() && b.getI() != b1.getI() && b.getX() != b1.getX()
&& b.getCList().size() == 2
&& b.getCList().containsAll(Arrays.asList(y, z)))
.collect(Collectors.toList());
if(b3List.size() == 1) {
// 如果存在B3单元格,则可以排除B2所在宫与B3所在行中,包含z的候选数字
List<Box> clearList = getXList(b3List.get(0)).stream()
.filter(b -> b.getG() == b2.getG() && b.isBlank() && b.getCList().contains(z))
.collect(Collectors.toList());
for(Box box : clearList) {
getListener().sendMsg("移除数字%d\n", z);
box.removeCList(z);
return box;
}
}
}
}
}
}
}
}
return null;
}
}
YXwingCalc
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.stream.Collectors;
/**
* 与XY-wing对应 <br/>
* 测试数据:SudoUtil.transpose(DataConstant.OTHER_XY_WING_01) 转置后的数独
*/
public class YXwingCalc extends AbstractCalc {
@Override
Box solve() {
Set<Map.Entry<Integer, List<Box>>> entries = getYMap().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() == 2) {
// 遍历B2单元格
for (Box b2 : list) {
// B2不能与B1在同一宫内,并且也只有两个候选数字,并且两个候选数字与B1不能完全相同
if(b2.isBlank() && b2.getG() != b1.getG() && b2.getCList().size() == 2 && !SudoUtil.isEqTwoList(b1.getCList(), b2.getCList())) {
// 确定数字x,为B1和B2中相同的数字
Integer x = SudoUtil.getEqContent(b1.getCList(), b2.getCList());
if(x != null) {
// 从B1中获取数字y,从B2中获取数字z
Integer y = SudoUtil.getOther(b1.getCList(), x);
Integer z = SudoUtil.getOther(b2.getCList(), x);
// 从B1所在宫内,获取根据数字y和z获取单元格B3,B3不能与B1在同一列
List<Box> b3List = getGList(b1).stream()
.filter(b -> b.isBlank() && b.getI() != b1.getI() && b.getY() != b1.getY()
&& b.getCList().size() == 2
&& b.getCList().containsAll(Arrays.asList(y, z)))
.collect(Collectors.toList());
if(b3List.size() == 1) {
// 如果存在B3单元格,则可以排除B2所在宫与B3所在列中,包含z的候选数字
List<Box> clearList = getYList(b3List.get(0)).stream()
.filter(b -> b.getG() == b2.getG() && b.isBlank() && b.getCList().contains(z))
.collect(Collectors.toList());
for(Box box : clearList) {
getListener().sendMsg("移除数字%d\n", z);
box.removeCList(z);
return box;
}
}
}
}
}
}
}
}
return null;
}
}
由于没有找到YX-wing的案例,所以通过矩阵转置来产生场景。在SoduUtil中添加转置代码
SudoUtil
新增方法如下:
/**
* 功能描述: 两个列表的内容是否一致 <br/>
*
* @param l1 list1
* @param l2 list2
* @return "boolean"
*/
public static boolean isEqTwoList(List<Integer> l1, List<Integer> l2) {
return l1.containsAll(l2) && l2.containsAll(l1);
}
/**
* 功能描述: 获取相同的内容 <br/>
*
* @param l1 list1
* @param l2 list2
* @return "java.lang.Integer"
*/
public static Integer getEqContent(List<Integer> l1, List<Integer> l2) {
List<Integer> c = l1.stream().filter(l2::contains).collect(Collectors.toList());
if(c.size() == 1) {
return c.get(0);
}
return null;
}
/**
* 功能描述: 获取其他的一个数字 <br/>
*
* @param list 列表
* @param i 待排除的数字
* @return "java.lang.Integer"
*/
public static Integer getOther(List<Integer> list, Integer i) {
List<Integer> c = list.stream().filter(l -> !l.equals(i)).collect(Collectors.toList());
if(c.size() == 1) {
return c.get(0);
}
return null;
}
Box
新增方法
/**
* 功能描述: 候选值移除数字 <br/>
*
* @param c 数字
*/
public void removeCList(Integer c) {
getCList().remove(c);
if(getCList().size() == 1) {
setVAndClear(getCList().get(0));
}
}
测试数据
在 DataConstant 中添加
/** XY-wing 测试数据 */
public static final String OTHER_XY_WING_01 = "092046083038000020000300500023090018910000350805031249786000435341857002259463871";
输出结果
5 ( ) 9 ( ) 2 ( ) | 0 (17 ) 4 ( ) 6 ( ) | 0 (17 ) 8 ( ) 3 ( ) |
0 (46 ) 3 ( ) 8 ( ) | {0}(59 ) 0 (17 ) 0 (59 ) | 0 (167 ) 2 ( ) 0 (467 ) |
1 ( ) 0 (67 ) 0 (47 ) | 3 ( ) 0 (28 ) 0 (28 ) | 5 ( ) 9 ( ) 0 (46 ) |
0 (46 ) 2 ( ) 3 ( ) | 0 (567 ) 9 ( ) 0 (45 ) | 0 (67 ) 1 ( ) 8 ( ) |
9 ( ) 1 ( ) 0 (47 ) | 0 (267 ) 0 (278 ) 0 (248 ) | 3 ( ) 5 ( ) 0 (67 ) |
8 ( ) 0 (67 ) 5 ( ) | 0 (67 ) 3 ( ) 1 ( ) | 2 ( ) 4 ( ) 9 ( ) |
7 ( ) 8 ( ) 6 ( ) | 0 (129 ) 0 (12 ) 0 (29 ) | 4 ( ) 3 ( ) 5 ( ) |
3 ( ) 4 ( ) 1 ( ) | 8 ( ) 5 ( ) 7 ( ) | 9 ( ) 6 ( ) 2 ( ) |
2 ( ) 5 ( ) 9 ( ) | 4 ( ) 6 ( ) 3 ( ) | 8 ( ) 7 ( ) 1 ( ) |
尝试【XY-wing 】
移除数字7
确认位置【行:5,列:9】 值为:【6】 候选值为:【】
5 ( ) 9 ( ) 2 ( ) | 0 (17 ) 4 ( ) 6 ( ) | 0 (17 ) 8 ( ) 3 ( ) |
0 (46 ) 3 ( ) 8 ( ) | 0 (59 ) 0 (17 ) 0 (59 ) | 0 (167 ) 2 ( ) 0 (47 ) |
1 ( ) 0 (67 ) 0 (47 ) | 3 ( ) 0 (28 ) 0 (28 ) | 5 ( ) 9 ( ) 0 (4 ) |
0 (46 ) 2 ( ) 3 ( ) | 0 (567 ) 9 ( ) 0 (45 ) | 0 (7 ) 1 ( ) 8 ( ) |
9 ( ) 1 ( ) 0 (47 ) | 0 (27 ) 0 (278 ) 0 (248 ) | 3 ( ) 5 ( ){6}( ) |
8 ( ) 0 (67 ) 5 ( ) | 0 (67 ) 3 ( ) 1 ( ) | 2 ( ) 4 ( ) 9 ( ) |
7 ( ) 8 ( ) 6 ( ) | 0 (129 ) 0 (12 ) 0 (29 ) | 4 ( ) 3 ( ) 5 ( ) |
3 ( ) 4 ( ) 1 ( ) | 8 ( ) 5 ( ) 7 ( ) | 9 ( ) 6 ( ) 2 ( ) |
2 ( ) 5 ( ) 9 ( ) | 4 ( ) 6 ( ) 3 ( ) | 8 ( ) 7 ( ) 1 ( ) |
最终结果
5 ( ) 9 ( ) 2 ( ) | 7 ( ) 4 ( ) 6 ( ) | 1 ( ) 8 ( ) 3 ( ) |
4 ( ) 3 ( ) 8 ( ) | 9 ( ) 1 ( ) 5 ( ) | 6 ( ) 2 ( ) 7 ( ) |
1 ( ) 6 ( ) 7 ( ) | 3 ( ) 8 ( ) 2 ( ) | 5 ( ) 9 ( ) 4 ( ) |
6 ( ) 2 ( ) 3 ( ) | 5 ( ) 9 ( ) 4 ( ) | 7 ( ) 1 ( ) 8 ( ) |
9 ( ) 1 ( ) 4 ( ) | 2 ( ) 7 ( ) 8 ( ) | 3 ( ) 5 ( ) 6 ( ) |
8 ( ) 7 ( ) 5 ( ) | 6 ( ) 3 ( ) 1 ( ) | 2 ( ) 4 ( ) 9 ( ) |
7 ( ) 8 ( ) 6 ( ) | 1 ( ) 2 ( ){9}( ) | 4 ( ) 3 ( ) 5 ( ) |
3 ( ) 4 ( ) 1 ( ) | 8 ( ) 5 ( ) 7 ( ) | 9 ( ) 6 ( ) 2 ( ) |
2 ( ) 5 ( ) 9 ( ) | 4 ( ) 6 ( ) 3 ( ) | 8 ( ) 7 ( ) 1 ( ) |
============数独解题完成,尝试次数为:49============
总结
在写代码的过程中,研究案例,并且思考相关变化。