使用Java Stream API转换分支界定循环

8

我有一个简单的分支界限算法,可以处理旅行商问题的一种变体,我想尝试将其转化为使用Java 8 Stream API。然而,在不依赖副作用的情况下进行转换时,我遇到了一些困难。

初始代码

int bound = Integer.MAX_VALUE;
List<Location> bestPath = null;

while(!queue.isEmpty()) {
    Node curr = queue.poll();
    //bound exceeds best, bail
    if (curr.getBound() >= bound) { 
        return bestPath;
    }
    //have a complete path, save it
    if(curr.getPath().size() == locations.size()) {
        bestPath = curr.getPath();
        bound = curr.getBound();
        continue;
    }
    //incomplete path - add all possible next steps
    Set<Location> unvisited = new HashSet<>(locations);
    unvisited.removeAll(curr.getPath());
    for (Location l : unvisited) {
        List<Location> newPath = new ArrayList<>(curr.getPath());
        newPath.add(l);
        Node newNode = new Node(newPath, getBoundForPath(newPath));
        if (newNode.getBound() <= bound){
            queue.add(newNode);
        }
    }
}

我来尝试使用 Stream API 进行转换,并得出以下结果: Java 8 版本
Consumer<Node> nodeConsumer = node -> {
    if(node.getPath().size() == locations.size() ) {
        bestPath = node.getPath();
        bound = node.getBound();
    } else {
        locations.stream()
            .filter(l -> !node.getPath().contains(l))
            .map(l -> {
                List<Location> newPath = new ArrayList<>(node.getPath());
                newPath.add(s);
                return new Node(newPath, getBoundForPath(newPath));
            })
            .filter(newNode -> newNode.getBound() <= bound)
            .forEach(queue::add);
    }
};

Stream.generate(() -> queue.poll())
    .peek(nodeConsumer)
    .filter(s -> s.getBound() > bound)
    .findFirst();

return bestPath;

主要问题在于nodeConsumer必须引用bestPath和bound,而它们不是final变量。我可以将它们作为final AtomicReference变量来解决此问题,但我觉得这有点违反了流API的精神。是否有人可以帮助我将初始算法简化为更符合惯用法的实现方式?

5
我认为,如果不滥用API,你很难获得更好的结果。Stream API 不适用于这种算法。尽管如此,这个问题还是很有趣的。 - Tagir Valeev
@TagirValeev 感谢您的回复。我还在适应新的选项,并且很难确定一件事情之所以困难是因为我做错了,还是因为它不是理想的用法。 - Alex Pritchard
1个回答

1
我想知道是否使用reduce是解决这个问题的方法,因为它允许您跟踪值而不需要外部变量。
类似以下内容(我必须猜测您上面代码的一些细节,但希望我走在正确的轨道上)。
    final BiFunction<Entry<Integer, List<Location>>, Node, Entry<Integer, List<Location>>> accumulator
        = (identity, node) -> {
            if (node.getPath().size() == locations.size() ) {
                return new SimpleEntry<>(node.getBound(), node.getPath());
            } else {
                locations.stream()
                    .filter(l -> !node.getPath().contains(l))
                    .map(l -> {
                        List<Location> newPath = new ArrayList<>(node.getPath());
                        newPath.add(l);
                        return new Node(newPath, getBoundForPath(newPath));
                    })
                    .filter(newNode -> newNode.getBound() <= identity.getKey())
                    .forEach(queue::add);
                return identity;
            }
        };

    final BinaryOperator<Entry<Integer, List<Location>>> combiner
        = (left, right) -> left.getKey() < right.getKey() ? left : right;

    final Entry<Integer, List<Location>> identity
        = new SimpleEntry<>(Integer.MAX_VALUE, null);

    final List<Location> bestValue = Stream.generate(queue::poll)
        .reduce(identity, accumulator, combiner)
        .getValue();

或者,您可以查看jOOλ中使用Seq(Stream的顺序扩展),并改用foldLeft


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接