简体   繁体   中英

Combine two streams Java 8

Is there a way to combine the following 2 Statesments?

    Map<Integer,Double> collX = listeAllerPunkte.stream().collect(groupingBy(DataPoint::getId,
                                averagingDouble(DataPoint::getX)));
    Map<Integer,Double> collY = listeAllerPunkte.stream().collect(groupingBy(DataPoint::getId,
                                averagingDouble(DataPoint::getY)));

I have a Class DataPoints like this:

public class DataPoint {

    public final double x;
    public final double y;
    private int Id;

    public DataPoint(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public int getId() {
        return Id;
    }
}

The Id contains a Random value between 0-5 .

listeAllerPunkte is a List with a lot of DataPoints

Now I want to create a DataPoint for each DataPoints in the List with the same Id . The DataPoint should have the average of the x and y values of the Datapoints with the same Id .

With the two Statemantes from the Beginning i must create the DataPoints manually out of the two Maps. Is there a way to create them directly in the stream?

A general solution would be using a collector which can combine two collectors to process both at once. Unfortunately, such collector does not exist in the standard API, but this answer provide an implementation of such a collector.

Alternatively, you can create a solution for this specific case by creating your own class for holding the summary of points, eg

static class DataPointSummary {
    long count;
    double sumX, sumY;

    public double getAverageX() {
        return count==0? 0: sumX/count;
    }
    public double getAverageY() {
        return count==0? 0: sumY/count;
    }
    public void add(DataPoint p) {
        count++;
        sumX+=p.getX();
        sumY+=p.getY();
    }
    public DataPointSummary merge(DataPointSummary s) {
        count+=s.count;
        sumX+=s.sumX;
        sumY+=s.sumY;
        return this;
    }
    @Override
    public String toString() {
        return "DataPointSummary["+count+" points"
            +", avg x="+getAverageX()+", avg y="+getAverageY()+']';
    }
}

Then you may collect your points like

Map<Integer,DataPointSummary> coll = listeAllerPunkte.stream().collect(
    groupingBy(DataPoint::getId, Collector.of(
        DataPointSummary::new, DataPointSummary::add, DataPointSummary::merge)));

Note that I assumed that you method signature public double getId() is a typo and actually public int getId() as otherwise, the examples in your question won't work.

The summary implementation above works well if the coordinates of the points have the same magnitude. If you encounter both, very large values and very small values within the same group, you may need a summing with error compensation algorithm. Instead of implementing it yourself, I recommend using the summary implementation of the JRE instead:

static class DataPointSummary {
    final DoubleSummaryStatistics x=new DoubleSummaryStatistics();
    final DoubleSummaryStatistics y=new DoubleSummaryStatistics();

    public double getAverageX() {
        return x.getAverage();
    }
    public double getAverageY() {
        return y.getAverage();
    }
    public void add(DataPoint p) {
        x.accept(p.getX());
        y.accept(p.getY());
    }
    public DataPointSummary merge(DataPointSummary s) {
        x.combine(s.x);
        y.combine(s.y);
        return this;
    }
    @Override
    public String toString() {
        return "DataPointSummary["+x.getCount()+" points"
            +", avg x="+getAverageX()+", avg y="+getAverageY()+']';
    }
}

This variant is used the same way as the first one.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM