Jordi
Jordi

Reputation: 23277

java streams: straightforward reduce

I've a stream of MetricGroup, where:

public class MetricGroup {

    private int uploadedDocs;
    private long uploadedKbs;

    // getters and setters

}

I need to sumarize all metrics in one single metric. I mean, I need to add all metric.uploadedDocs into a sumMetric.uploadedDocs and metric.uploadedKds into a sumMetric.uploadedKbs.

I figure out I need some kind of reduce

Stream.of(new MetricGroup(1,100), new MetricGroup(1,200))
    .reduce(????);

Any ideas?

Upvotes: 6

Views: 187

Answers (5)

Stuart Marks
Stuart Marks

Reputation: 132590

If you want to use reduction, I'd recommend making MetricGroup be a value type, by making the fields final, adding a zero, and replacing the setters with combining methods.

public class MetricGroup {
    private final int uploadedDocs;
    private final long uploadedKbs;

    // obvious constructor
    // getters

    public static final ZERO = new MetricGroup(0, 0);

    public MetricGroup add(MetricGroup a, MetricGroup b) {
        return new MetricGroup(a.uploadedDocs + b.upLoadedDocs,
                               a.uploadedKbs + b.uploadedKbs);
    }

    public MetricGroup uploadOneDoc(long kbs) {
        return new MetricGroup(uploadedDocs + 1, uploadedKbs + kbs);
    }
}

This will let you perform stream operations nicely:

MetricGroup sum = metricGroups.stream()
                              .reduce(MetricGroup.ZERO, MetricGroup::add);

Upvotes: 1

Sweeper
Sweeper

Reputation: 274480

You can use this overload of reduce:

T reduce(T identity,
     BinaryOperator<T> accumulator)

like this:

.reduce(new MetricGroup(0, 0),
        (x, y) -> new MetricGroup(
                      x.getUploadedDocs() + y.getUploadedDocs()
                      x.getUploadedKbs() + y.getUploadedKbs()
                  )
        )

You can also use the collect method:

private static MetricGroup combine(MetricGroup x, MetricGroup y) {
    x.setUploadedDocs(x.getUploadedDocs() + y.getUploadedDocs());
    x.setUploadedKbs(x.getUploadedKbs() + y.getUploadedKbs());
    return x;
}

// ....

.collect(() -> new MetricGroup(0, 0),
    YourClass::combine,
    YourClass::combine
)

Upvotes: 7

Lino
Lino

Reputation: 19910

As always with java streams, you don't really have to use them. I suggest creating a simple reduction helper-method:

public static MetricGroup reduce(Iterable<? extends MetricGroup> metrics){
   int uploadedDocs = 0;
   long uploadedKbs = 0L;
   for(MetricGroup metric : metrics){
       uploadedDocs += metric.getUploadedDocs();
       uploadedKbs += metric.getUploadedKbs();
   }
   return new MetricGroup(uploadedDocs, uploadedKbs);
}

If you can't change that you start with a stream you can still use above method, by just passing a reference to the Stream.iterator() method:

MetricGroup reduced = reduce(stream::iterator);

Upvotes: 2

Ousmane D.
Ousmane D.

Reputation: 56469

To avoid the creation of several/many MetricGroup objects during the reduce call, you can make two separate calls to sum the UploadedDocs and UploadedKbs respectively and then construct a new MetricGroup representing the result.

int uploadedDocsSum = source.stream().mapToInt(MetricGroup::getUploadedDocs).sum();
long uploadedKbsSum = source.stream().mapToLong(MetricGroup::getUploadedKbs).sum();
MetricGroup result = new MetricGroup(uploadedDocsSum, uploadedKbsSum);

Arguably more readable as well...

Upvotes: 3

baao
baao

Reputation: 73281

Simply pass in a single lambda (will manipulate existing MetricGroup)

Stream.of(new MetricGroup(1, 100), new MetricGroup(1, 200))
    .reduce((a, b) -> {
      a.setUploadedDocs(a.getUploadedDocs() + b.getUploadedDocs());
      a.setUploadedKbs(a.getUploadedKbs() + b.getUploadedKbs());
      return a;
    });

// Optional[F.MetricGroup(uploadedDocs=2, uploadedKbs=300)]

Or, to really get a new MetricGroup (without manipulating an existing one)

Stream.of(new MetricGroup(1, 100), new MetricGroup(1, 200))
        .reduce((a, b) -> new MetricGroup(a.getUploadedDocs() + b.getUploadedDocs(), a.getUploadedKbs() + b.getUploadedKbs()));

Upvotes: 2

Related Questions