Search code examples
javacollectionsjava-8java-stream

How to get the length of a path using Java 8 streams


I have List<Vector3D> , where Vector3D is a coordinate. Vector3D has method double distance(Vector3D) which returns the distance between two positions.

I want to find sum of all distances between Vector3D elements of the list. I want to find it using Java 8 streams. I tried to use reduce but it can't help me.

For example, I have a list with (1,0,0) (2,0,0) (3,0,0). The length of this path should be 3.

If we are using Java 7 or lower we have to do:

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;
}

Solution

  • The operation you are performing is called Mutable reduction.

    Pshemo’s answer shows how you can implement such operation ad-hoc by providing the three necessary functions. However, when all three functions are implemented by a dedicated class it might be useful to implement these functions inside a class implementing Collector for easier reuse:

    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;
        }
    }
    

    You will recognize the three function from the ad-hoc version. New is a fourth function, finisher which allows to specify how the final result can be extracted from the mutable container so we don’t need the getSum() call.

    The use case simplifies to:

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