Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the (kind of) inverse operation to Java's Stream.flatMap()?

The Stream.flatMap() operation transforms a stream of

a, b, c

into a stream that contains zero or more elements for each input element, e.g.

a1, a2, c1, c2, c3

Is there the opposite operations that batches up a few elements into one new one?

  • It is not .reduce(), because this produces only one result
  • It is not collect(), because this only fills a container (afaiu)
  • It is not forEach(), because this has returns just void and works with side effects

Does it exist? can I simulate it in any way?

like image 898
Harald Avatar asked Jun 15 '18 13:06

Harald


People also ask

What does the flatMap () function do?

The flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level. It is identical to a map() followed by a flat() of depth 1 ( arr. map(... args).

What does flatMap do in stream?

flatMap. Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents have been placed into this stream.

Is flatMap a terminal operation?

flatMap() is not terminal operation.

What is difference between map () and flatMap () and reduce () method in Java stream?

Both map and flatMap can be applied to a Stream<T> and they both return a Stream<R> . The difference is that the map operation produces one output value for each input value, whereas the flatMap operation produces an arbitrary number (zero or more) values for each input value.


2 Answers

Finally I figured out that flatMap is its own "inverse" so to say. I oversaw that flatMap not necessarily increases the number of elements. It may also decrease the number of elements by emitting an empty stream for some of the elements. To implement a group-by operation, the function called by flatMap needs minimal internal state, namely the most recent element. It either returns an empty stream or, at the end of a group, it returns the reduced-to group representative.

Here is a quick implementation where groupBorder must return true if the two elements passed in do not belong to the same group, i.e. between them is the group border. The combiner is the group function that combines, for example (1,a), (1,a), (1,a) into (3,a), given that your group elements are, tuples (int, string).

public class GroupBy<X> implements Function<X, Stream<X>>{

  private final BiPredicate<X, X> groupBorder;
  private final BinaryOperator<X> combiner;
  private X latest = null;

  public GroupBy(BiPredicate <X, X> groupBorder,
                 BinaryOperator<X> combiner) {
    this.groupBorder = groupBorder;
    this.combiner = combiner;
  }

  @Override
  public Stream<X> apply(X elem) {
    // TODO: add test on end marker as additonal parameter for constructor
    if (elem==null) {
      return latest==null ? Stream.empty() : Stream.of(latest);
    }
    if (latest==null) {
      latest = elem;
      return Stream.empty();
    }
    if (groupBorder.test(latest, elem)) {
      Stream<X> result = Stream.of(latest);
      latest = elem;
      return result;
    }
    latest = combiner.apply(latest,  elem);
    return Stream.empty();
  }
}

There is one caveat though: to ship the last group of the whole stream, an end marker must be stuck as the last element into the stream. The above code assumes it is null, but an additional end-marker-tester could be added.

I could not come up with a solution that does not rely on the end marker.

Further I did not also convert between incoming and outgoing elements. For a unique-operation, this would just work. For a count-operation, a previous step would have to map individual elements to a counting object.

like image 119
Harald Avatar answered Sep 28 '22 16:09

Harald


You can hack your way around. See the following example:

Stream<List<String>> stream = Stream.of("Cat", "Dog", "Whale", "Mouse")
   .collect(Collectors.collectingAndThen(
       Collectors.partitioningBy(a -> a.length() > 3),
       map -> Stream.of(map.get(true), map.get(false))
    ));
like image 32
Lino Avatar answered Sep 28 '22 16:09

Lino