Hi, I’ve created one of my own and I’d happily toss it for a standard implementation.
— Kirk > On Jun 11, 2018, at 8:10 PM, Paul Sandoz <[email protected]> wrote: > > 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 <[email protected]> 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 >> >
