On 06/15/18 13:31, Tagir Valeev wrote:
Peter,

>        EnumSet<Characteristics> intersection =
EnumSet.copyOf(keyCollector.characteristics());
>          intersection.retainAll(valCollector.characteristics());
>          return intersection;

Please note that `copyOf` should either receive an EnumSet or non-empty Collection to obtain the enum class. This is not guaranteed by `characteristics()` implementation, thus failure is possible.

Testcase:
new BiCollector<>(Collectors.counting(), Collectors.toSet()).characteristics();
Fails with
Exception in thread "main" java.lang.IllegalArgumentException: Collection is empty
at java.base/java.util.EnumSet.copyOf(EnumSet.java:173)
at BiCollector.characteristics(BiCollector.java:77)
at Main.main(Main.java:21)

Yeah, it came to my mind after I posted the example implementation. A case where API design sometimes misleads even experienced programmers, because it re-uses an idiom that is usually right (for most other Set(s)), but fails for that particular case. EnumSet needs an explicit enum type, so it should have been provided as an explicit argument...

Regards, Peter



With best regards,
Tagir Valeev.

On Mon, Jun 11, 2018 at 7:40 PM Peter Levart <peter.lev...@gmail.com <mailto:peter.lev...@gmail.com>> wrote:

    Hi,

    Have you ever wanted to perform a collection of the same Stream
    into two
    different targets using two Collectors? Say you wanted to collect
    Map.Entry elements into two parallel lists, each of them
    containing keys
    and values respectively. Or you wanted to collect elements into 
    groups
    by some key, but also count them at the same time? Currently this
    is not
    possible to do with a single Stream. You have to create two identical
    streams, so you end up passing Supplier<Stream> to other methods
    instead
    of bare Stream.

    I created a little utility Collector implementation that serves the
    purpose quite well:

    /**
      * A {@link Collector} implementation taking two delegate
    Collector(s)
    and producing result composed
      * of two results produced by delegating collectors, wrapped in
    {@link
    Map.Entry} object.
      *
      * @param <T> the type of elements collected
      * @param <K> the type of 1st delegate collector collected result
      * @param <V> tye type of 2nd delegate collector collected result
      */
    public class BiCollector<T, K, V> implements Collector<T,
    Map.Entry<Object, Object>, Map.Entry<K, V>> {
         private final Collector<T, Object, K> keyCollector;
         private final Collector<T, Object, V> valCollector;

         @SuppressWarnings("unchecked")
         public BiCollector(Collector<T, ?, K> keyCollector,
    Collector<T, ?,
    V> valCollector) {
             this.keyCollector = (Collector)
    Objects.requireNonNull(keyCollector);
             this.valCollector = (Collector)
    Objects.requireNonNull(valCollector);
         }

         @Override
         public Supplier<Map.Entry<Object, Object>> supplier() {
             Supplier<Object> keySupplier = keyCollector.supplier();
             Supplier<Object> valSupplier = valCollector.supplier();
             return () -> new
    AbstractMap.SimpleImmutableEntry<>(keySupplier.get(),
    valSupplier.get());
         }

         @Override
         public BiConsumer<Map.Entry<Object, Object>, T> accumulator() {
             BiConsumer<Object, T> keyAccumulator =
    keyCollector.accumulator();
             BiConsumer<Object, T> valAccumulator =
    valCollector.accumulator();
             return (accumulation, t) -> {
                 keyAccumulator.accept(accumulation.getKey(), t);
                 valAccumulator.accept(accumulation.getValue(), t);
             };
         }

         @Override
         public BinaryOperator<Map.Entry<Object, Object>> combiner() {
             BinaryOperator<Object> keyCombiner = keyCollector.combiner();
             BinaryOperator<Object> valCombiner = valCollector.combiner();
             return (accumulation1, accumulation2) -> new
    AbstractMap.SimpleImmutableEntry<>(
                 keyCombiner.apply(accumulation1.getKey(),
    accumulation2.getKey()),
                 valCombiner.apply(accumulation1.getValue(),
    accumulation2.getValue())
             );
         }

         @Override
         public Function<Map.Entry<Object, Object>, Map.Entry<K, V>>
    finisher() {
             Function<Object, K> keyFinisher = keyCollector.finisher();
             Function<Object, V> valFinisher = valCollector.finisher();
             return accumulation -> new
    AbstractMap.SimpleImmutableEntry<>(
                 keyFinisher.apply(accumulation.getKey()),
                 valFinisher.apply(accumulation.getValue())
             );
         }

         @Override
         public Set<Characteristics> characteristics() {
             EnumSet<Characteristics> intersection =
    EnumSet.copyOf(keyCollector.characteristics());
    intersection.retainAll(valCollector.characteristics());
             return intersection;
         }
    }


    Do you think this class is general enough to be part of standard
    Collectors repertoire?

    For example, accessed via factory method Collectors.toBoth(Collector
    coll1, Collector coll2), bi-collection could then be coded simply as:

             Map<String, Integer> map = ...

             Map.Entry<List<String>, List<Integer>> keys_values =
                 map.entrySet()
                    .stream()
                    .collect(
                        toBoth(
                            mapping(Map.Entry::getKey, toList()),
                            mapping(Map.Entry::getValue, toList())
                        )
                    );


             Map.Entry<Map<Integer, Long>, Long> histogram_count =
                 ThreadLocalRandom
                     .current()
                     .ints(100, 0, 10)
                     .boxed()
                     .collect(
                         toBoth(
                             groupingBy(Function.identity(), counting()),
                             counting()
                         )
                     );


    Regards, Peter


Reply via email to