6

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?

4
  • Do you want to edit an existing one or do you need to create a new MetricGroup for the result? Commented Jan 8, 2019 at 10:51
  • IMHO it would be simpler and easier to undertand and to maintain to use two simple reduce/sum instead of one overly-complicated one. Commented Jan 8, 2019 at 10:52
  • @tobias_k good shout indeed!, I had a similar idea as of reading this (posted an answer below). hopefully, that's alright with you :) Commented Jan 8, 2019 at 11:20
  • Please consider accepting an answer. Or is there something else you want to ask? Commented Jan 9, 2019 at 11:26

5 Answers 5

7

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 ) 
Sign up to request clarification or add additional context in comments.

2 Comments

I think this is the best solution because it doesn't manipulate existing MetricGroups
@FedericoPeraltaSchaffner You're right. I was thinking about collect.
3

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...

4 Comments

In the worst case it's just one object that's created with the other approches. Also you have two streams while the other solutions work with one. But yours is probably more readable.
IMHO the only problem with this approach might be if OP does only has a reference to that one stream instance and it's too large to be buffered in a list.
@tobias_k my bad, I was working on another problem on my IDE and forgot to accommodate it here. edited now.
@user489872 On the other hand, the other approaches have 2*n calls to the getters and another n calls to the setters, whereas this only does n calls to the getter.
2

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())); 

Comments

2

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

Comments

1

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

1 Comment

Good answer, I really like the constant ZERO metric group

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.