用Stream API写八皇后

2016-12-21  本文已影响0人  风干鸡

写出来是这样:

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检测当前棋子与被检测的列、左斜线、又斜线是否冲突,都不满足的话递归求下一个位置。

这里的终止条件有两个,psnull和被检测的棋子与当前棋子冲突,否则递归,所以可以轻松的转换为非递归写法:

        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的所有解。
步骤:

  1. IntStream.rangeClosed(1, 8)枚举所有可能的位置
  2. filter(y -> isSafe(ps, y, 1)) 过滤掉不合法的位置
  3. boxed() 装箱,IntStream转换为Stream<Integer>
  4. 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依然是可以正确运行的,非常费解。

上一篇下一篇

猜你喜欢

热点阅读