7

I currently have the following situation:

I have got a Report object which can contain multiple Query objects. The Query objects have properties: Optional<Filter> comparisonFilter, Optional<String> filterChoice and int queryOutput.

Not every query has a comparison filter, so I first check on that. Then, I make sure I get the queries for a particular filter (which is not the problem here, so I will not discuss this in detail). Every filter has some choices, of which the number of choices is variable.

Here is an example of the input (these Query objects all have the same comparisonFilter):

Query 1 -- Choice: 'First' -- Output: 10
Query 1 -- Choice: 'First' -- Output: 5
Query 1 -- Choice: 'Second' -- Output: 25
Query 1 -- Choice: 'Third' -- Output: 10

Now, I would like to sum the query outputs for every unique choice. I currently have this code:

report
.getQueries()
.stream()
.filter(q -> q.getComparisonFilter().isPresent())
.filter(q -> q.getComparisonFilter().get().equals(view.getFilter().get()))
.forEach(query -> {

    //Sum the query outputs per choice

});

I could do this by creating a Map<String, Integer>, where the key is the choice and the value is the query input. But then I would need to loop through the Map again to use the value for something (which is not important here).

The output should be like this:

Choice: 'First' -- Summed Output: 15
Choice: 'Second' -- Summed Output: 25
Choice: 'Third' -- Summed Output: 10

But I would like to use this 'Summed Output' directly in a forEach on the stream, but if this is not possible or practical anymore, I am okay with that.

I would like to do this the 'Java 8'-way, but I can not seem to find out how.

So my question is: Is it possible to do this shorter with the new Stream API?

Note: If anyone has some ideas about how I could make this question more general (maybe a better title and some generalizations), please let me know!

Alexis C.
  • 91,686
  • 21
  • 171
  • 177
bashoogzaad
  • 4,611
  • 8
  • 40
  • 65
  • 1
    It sounds as if you have this problem: http://www.leveluplunch.com/java/examples/java-util-stream-groupingBy-example/ –  Feb 18 '15 at 14:37
  • Thanks for your comment @LutzHorn, it is a very helpful link. I get the `groupingBy` method, and then I could stream the `keySet`, but is it then also possible to sum the `queryInput` for every choice from within the stream? Maybe some reduction is possible? – bashoogzaad Feb 18 '15 at 14:50
  • 2
    @bashoogzaad I'm not sure I get what do you want to do; especially the use of `Optional filterChoice`. Do you want to sum the queries from the value in this optional or from the values in the optional which contains the Filter? Is it possible to have an example of input/output? – Alexis C. Feb 18 '15 at 14:51

1 Answers1

7

If I understand well, you are indeed looking for a groupingBy and then you have to group the values by summing their int property.

The groupingBy will give you a Map<String, List<Query>> but then the downstream collector (Collectors.summingInt in this case) will sum all the int values of the Query instances in the list, resulting in a Map<String, Integer>.

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.summingInt;
...

Map<String, Integer> map = 
    report.getQueries()
          .stream()
          .filter(q -> q.getComparisonFilter().isPresent())
          .filter(q -> q.getComparisonFilter().get().equals(view.getFilter().get()))
          .collect(groupingBy(q -> q.filterChoice.get(), summingInt(q -> q.queryOutput)));

Note that you should check whether the filterChoice Optional is not empty (maybe add another filter clause?). You can see this small gist for a basic and simplified demo to illustrate the principle.

Also the Optional class provide a sensible implementation of equals so the filter clause could looks like this:

.filter(q -> q.getComparisonFilter().equals(view.getFilter()))
Alexis C.
  • 91,686
  • 21
  • 171
  • 177
  • Thanks for you great answer! It seems to work in the gist, but I get an error that `Map` can not be cast to `Map`. I think it has something to do with my code, but the filterChoice is just a `Optional`. I wil try to figure this out, and accept your answer. – bashoogzaad Feb 18 '15 at 15:25
  • 2
    @bashoogzaad Do you compile with Eclipse or Javac? Eclipse compiler has still some troubles with type inference (and with some Java 8 features in general). Try to compile it with javac. If it doesn't compile; either you made an error or you would have to help the compiler by providing the type ;-) – Alexis C. Feb 18 '15 at 15:26
  • I am using Eclipse compiler so that could be the problem. I will look into my code some further, but I think it is just my mistake somewhere. Now Eclipse even says: cannot convert `Map` to `Map` – bashoogzaad Feb 18 '15 at 15:28