【问题标题】:Convert Branch and Bound loop to use Java Stream API将 Branch and Bound 循环转换为使用 Java Stream API
【发布时间】:2015-09-30 00:42:20
【问题描述】:

我有一个简单的分支定界算法,它适用于旅行商问题的变体,我认为尝试将其转换为使用 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 变量。我可以让它们成为最终的 AtomicReference 变量来解决这个问题,但我觉得这违反了流 API 的精神。谁能帮我将初始算法提炼成更惯用的实现?

【问题讨论】:

  • 我不认为你可以在不滥用 API 的情况下获得更好的东西。 Stream API 不适用于此类算法。不过这个问题很有趣。
  • @TagirValeev 感谢您的回复。仍然习惯于我可用的新选项,并且很难确定某件事是因为我做错了而困难,还是因为它不是理想的用法而困难。

标签: java algorithm lambda java-8 java-stream


【解决方案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λ(Streams 的顺序扩展)中使用Seq,并改用foldLeft

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-05
    • 1970-01-01
    • 1970-01-01
    • 2022-10-13
    • 1970-01-01
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多