3

If I have to generate two groups based on two different fields using Stream, this is one approach I can take:

var prop1Group = beans.stream().collect(Collectors.groupingBy(Bean::getProp1));
var prop2Group = beans.stream().collect(Collectors.groupingBy(Bean::getProp2));

But this approach iterates over the list twice. In an imperative way, I can get the same result in single iteration, like this:

var prop1Group = new HashMap<String, Set<Bean>>();
var prop2Group = new HashMap<String, Set<Bean>>();

for (var bean : beans) {
    prop1Group.computeIfAbsent(bean.getProp1(), key -> new HashSet<>()).add(bean);
    prop2Group.computeIfAbsent(bean.getProp2(), key -> new HashSet<>()).add(bean);
}

Is there anyway to do get the same done in declarative way using streams, without iterating twice?

Gopal S Akshintala
  • 1,933
  • 1
  • 17
  • 24
  • 6
    In Java 12, you can use [the `teeing` collector](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/stream/Collectors.html#teeing(java.util.stream.Collector,java.util.stream.Collector,java.util.function.BiFunction)), however, I don’t think that “iterating twice” is an actual problem for an ordinary collection. That sounds more like premature optimization. In fact, given how JIT and Hotspot optimizer work, the solution iterating twice but performing one operation in the inner loop may turn out to be faster. – Holger Sep 04 '19 at 08:28

2 Answers2

2

As per the comment from @Holger, I could write it more declaratively with teeing, like this, but this still costs 2N

beans.stream().collect(
                Collectors.teeing(
                        groupingBy(Bean::getProp1),
                        groupingBy(Bean::getProp2),
                        List::of)) 
Gopal S Akshintala
  • 1,933
  • 1
  • 17
  • 24
1

Since you've added vavr as a tag, I would also want to add another solution, that's using tuples, pattern matching and folding:

var result = list.foldLeft(
      Tuple.of(List.empty(), List.empty()),
      (lists, element) -> Match(element).of(
           Case($(Bean::getProp1), lists.map1(l -> l.append(element))),
           Case($(Bean::getProp2), lists.map2(l -> l.append(element)))
));

In this case groups will be stored as fields of Tuple2.

Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76