用Stream API写八皇后
写出来是这样:
public class Test {
static class Pos {
int val;
Pos next;
Pos(int val, Pos next) {
this.val = val;
this.next = next;
}
}
static boolean isSafe(Pos ps, int y, int gap) {
return ps == null|| ps.val != y && ps.val + gap != y && ps.val - gap != y && isSafe(ps.next, y, gap + 1);
}
public static void main(String[] args) {
System.out.println(Stream.iterate(Stream.of((Pos)null), r -> r.flatMap(ps -> IntStream.rangeClosed(1, 8).filter(y -> isSafe(ps, y, 1)).boxed().map(y -> new Pos(y, ps)))).skip(8).findFirst().orElse(null).count());
}
}
Pos类
这是一个链表,成员属性是皇后放在哪一列,而放在哪一行隐含在元素在链表中的位置,比如链表的第一个元素的位置就是(1,val)。自定义类是为了能够方便的把新的位置拼接到原来的位置链表上而不用修改他,比如new Pos(y,ps)
,就是把新的位置放在第y列上。
isSafe方法
这里Pos
为空代表初始状态,棋盘是0*0的解,0*0棋盘解的集合就是Stream.of((Pos)null)
。 转为null是类型推导的要求,需要表达式类型为Stream<Pos>。因为行号隐藏在链表所在的位置,所以行就不用检测了。
拆开如下:
if(ps==null){
return true;
}else if (ps.val == y || ps.val + gap == y || ps.val - gap == y) {
return false;
} else {
return isSafe(ps.next, y, gap + 1);
}
ps
代表一个解,也就是一组位置的集合,而y代表接下来要放入的棋子所在的列。ps==null
代表前0*0棋盘没有放入任何棋子,所以第一枚棋子放哪里都可以。ps.val == y || ps.val + gap == y || ps.val - gap == y
检测当前棋子与被检测的列、左斜线、又斜线是否冲突,都不满足的话递归求下一个位置。
这里的终止条件有两个,ps
为null
和被检测的棋子与当前棋子冲突,否则递归,所以可以轻松的转换为非递归写法:
while (ps!=null && ps.val != y && ps.val + gap != y && ps.val - gap != y){
ps = ps.next;
gap++;
}
return ps == null;
插一个题外话,null可以赋值给和强转为任何引用类型(类和接口)。
4.1. The Kinds of Types and Values
The null reference can always be assigned or cast to any reference type (§5.2, §5.3, §5.5).
核心部分
r -> r.flatMap(ps -> IntStream.rangeClosed(1, 8).filter(y -> isSafe(ps, y, 1)).boxed().map(y -> new Pos(y, ps)))
r
代表前位置集合的流,也就是所有的解,比如当前生成8*8的所有解,r就代表7*7的所有解。
步骤:
-
IntStream.rangeClosed(1, 8)
枚举所有可能的位置 -
filter(y -> isSafe(ps, y, 1))
过滤掉不合法的位置 -
boxed()
装箱,IntStream
转换为Stream<Integer>
-
map(y -> new Pos(y, ps))
将合法位置拼装到原来的解上面
补充:
这里用的是Stream.iterate
,初始状态为Stream.of((Pos)null)
,每一次迭代新的流前一个流就被打开过了,所有这里想按顺序输出1*1,2*2 ...棋盘是不行的。而我们知道迭代八次之后就是想要的东西所以skip(8).findFirst().orElse(null)
,不知道有没有更简洁的写法。
用Stream.reduce
也是可行的,几乎没有差别:
IntStream.rangeClosed(1, 8).boxed().reduce(Stream.of((Pos) null), (r, i) -> r.flatMap(ps -> IntStream.rangeClosed(1, 8).filter(y -> isSafe(ps, y, 1)).boxed().map(y -> new Pos(y, ps))), Stream::concat).count()
其中Stream.reduce
这个地方的第三个参数combiner
我没有很明白,它不能为空,而把他替换为(a,b)->null
依然是可以正确运行的,非常费解。
完