Hi Peter,

I like it and can see it being useful, thanks for sharing. 

I am hesitating a little about it being in the JDK because there is the larger 
abstraction of a BiStream, where a similar form of collection would naturally 
fit (but perhaps without the intersection constraints for the 
characteristics?). We experimented a few times with BiStream and got quite far 
but decided pull back due to the lack of value types and specialized generics. 
So i dunno how this might turn out in the future and if your BiCollector fits 
nicely into such a future model.

What are you thoughts on this?

FWIW i would call it a “splitting” or “bisecting" collector e.g. 
“s.collect(bisecting(…))”

Paul.




> On Jun 11, 2018, at 5:39 AM, Peter Levart <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