【问题标题】:How to get length of path using java 8 streams如何使用java 8流获取路径长度
【发布时间】:2014-08-12 10:59:22
【问题描述】:

我有 List<Vector3D> ,其中 Vector3D 是一个坐标。我想找到列表中Vector3D 元素之间所有距离的总和。我想使用 java 8 流找到它。我尝试使用reduce,但它无法帮助我。

UPD:

Vector3D 有方法double distance(Vector3D) 女巫找到两个位置之间的距离。例如我有(1,0,0)(2,0,0)(3,0,0)的列表。结果,我想找到这条路径的长度。现在是 3。

如果我们使用的是 java 7 或更低版本,我们必须这样做:

public static double calcPathLength(List<Vector3D> path){
    double length = 0d;
    for (int i=0; i< path.size()-1; i++){
        length += path.get(i).distance(path.get(i+1));
    }
    return length;
}

【问题讨论】:

  • 当我假设正确时,您需要一对向量来计算它们的距离,您可能不直接使用 reduce,因为它适用于每个元素,但您可以计算距离列表然后减少此列表为单个结果。
  • 如何使用 java 8 流计算距离列表?
  • (1,0,0) (2,0,0) (3,0,0)之间的路径是直线,所以它的长度不应该是2而不是3吗?
  • 恐怕流在这里没有更大的用处,因为您需要一个特殊的流为您提供两个后续元素而不是单独的当前元素 - 所以计算距离将与
  • 这需要Collector

标签: java collections java-8 java-stream


【解决方案1】:

您正在执行的操作称为Mutable reduction

Pshemo’s answer 展示了如何通过提供三个必要的功能来实现这种临时操作。但是,当所有三个函数都由专用类实现时,在实现 Collector 的类中实现这些函数可能会很有用,以便于重用:

public class Distance implements Collector<Vector3D, Distance.Helper, Double> {

    public static final Distance COLLECTOR = new Distance();

    static final class Helper {
        private double sum = 0;
        private Vector3D first = null, previous = null;
    }
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
    public Supplier<Helper> supplier() {
        return Helper::new;
    }
    public BiConsumer<Helper, Vector3D> accumulator() {
        return (helper,vector3d)-> {
            if (helper.previous != null)
                helper.sum += vector3d.distance(helper.previous);
            else helper.first = vector3d;
            helper.previous = vector3d;
        };
    }
    public BinaryOperator<Helper> combiner() {
        return (h1,h2)-> {
            h2.sum += h1.sum;
            if(h1.previous!=null && h2.first!=null) {
                h2.sum += h1.previous.distance(h2.first);
                h2.first=h1.first;
            }
            return h2;
        };
    }
    public Function<Helper, Double> finisher() {
        return helper -> helper.sum;
    }
}

您将从 ad-hoc 版本中识别出这三个功能。新的是第四个函数finisher,它允许指定如何从可变容器中提取最终结果,因此我们不需要getSum() 调用。

用例简化为:

List<Vector3D> list;
//…
double distance=list.stream().collect(Distance.COLLECTOR);

【讨论】:

  • 您可能每次都想要一个新的收集器,而不是重复使用相同的静态实例。
  • @dkatzel:为什么?收集器是无状态的;创建多个实例没有意义。不要将Collector 与可变的Helper 容器混淆,后者将为每个可变归约重新实例化,甚至为单个并行归约进行多次实例化。此处显示的收集器是线程安全的,主要是因为该框架旨在使其变得如此简单。
  • 组合器没有做好它的工作。它通过测量两个子链端点之间的距离来增加两个子链的距离。通常,您不能期望子链按顺序组合,因此组合器和 Helper 类要允许这种组合要复杂得多。无论如何都不会调用组合器,因为流需要是顺序的,否则累加器也无法正常工作。
  • @Mark Jeronimus:关于端点你是对的,但关于顺序是错误的,因为 explained here 对于 ordered 流,组合器将以保留的方式使用订单,除非Collector 报告UNORDERED 特征。
【解决方案2】:

其中一个选项是创建一些帮助类,该类会记住以前使用的向量并基于它计算它与当前向量之间的差异。这个类可能看起来像

class DistanceHelper {
    private double sum = 0;
    private Vector3D first = null;
    private Vector3D last = null;

    public void add(Vector3D vector3d) {
        if (first == null)
            first = vector3d;
        if (last != null)
            sum += vector3d.distance(last);
        last = vector3d;
    }

    public void combine(DistanceHelper otherHelper) {
        //add distance of path from current thread with distance of path
        //from other thread
        sum += otherHelper.sum;
        //also add distance between paths handled by separate threads like
        // when path of Thread1 is A->B and Thread2 is C->D then we need to 
        // include path from `B` to `C`
        if (this.last!=null && otherHelper.first!=null)
            sum += this.last.distance(otherHelper.first);
        this.last = otherHelper.last;
    }

    public double getSum() {
        return sum;
    }
}

例如,您可以将它与combine 一起使用,而不是reduce 之类的

double sum = list
        .stream()//or parallelStream()
        .collect(DistanceHelper::new, DistanceHelper::add,
                DistanceHelper::combine).getSum();

【讨论】:

  • 为什么不让DistanceHelper实现Collector?这将大大简化用例。
  • @Holger 你可能是对的,但我从来没有真正实现过这个接口(我不经常使用 Java 8)而且我对它不是很熟悉,所以请随时发布另一个答案收集器实现示例。
  • 我注意到一个小缺陷:combine 方法没有添加两个DistanceHelpers 之间的距离。如果Stream 拆分为两个非空流,则拆分位置的距离将丢失。
  • @Holger 你是对的,并行性在这里让事情变得复杂。如果我们从不同的点开始构建两条路径,那么我们还需要包括第一条路径的末端和第二条路径的起点之间的距离(例如在点 A B C D 的情况下,当我们想要组合路径 A-&gt;BC-&gt;D 时,我们还需要包括B-&gt;C)。我不确定路径将以哪种顺序组合,所以我怀疑也许我们最终会将第二条路径与第一条路径组合,所以我们使用第一条路径的最后一个点和最后一条路径的第一个点会给我们A-&gt;D,这将是完全错误。
  • 最安全的选项是不涉及并行性,或者以某种方式保证元素的顺序为combined。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多