In this form (not locked to Map.Entry) it seems like a candidate for the JDK. The implementation is private, so it would be one public method that provides a new way to process streams.
Stephen On 14 June 2018 at 05:56, Tagir Valeev <amae...@gmail.com> wrote: > Hello! > > In my StreamEx library I created a "pairing" collector which does similar > job, but allows user to decide how to combine the results of two > collectors. This adds more flexibility. The signature is like this: > > public static <T, A1, A2, R1, R2, R> Collector<T, ?, R> pairing( > Collector<? super T, A1, R1> c1, > Collector<? super T, A2, R2> c2, > BiFunction<? super R1, ? super R2, ? extends R> finisher) > > Having such collector, The proposed `toBoth(c1, c2)` can be implemented as > simple as `pairing(c1, c2, Map::entry)`. OTOH if somebody wants to use > their own Pair class, it would be `pairing(c1, c2, Pair::new)`. > Sometimes you don't need a pair, but can create compound result object > right here. E.g.: > > Collector<BigDecimal, ?, BigDecimal> summing = > Collectors.reducing(BigDecimal.ZERO, BigDecimal::add); > Collector<BigDecimal, ?, BigDecimal> averaging = > pairing(summing, Collectors.counting(), (sum, count) -> > sum.divide(BigDecimal.valueOf(count), > RoundingMode.HALF_EVEN)); > > So locking to Map.Entry is entirely unnecessary. > > With best regards, > Tagir Valeev. > > > > On Thu, Jun 14, 2018 at 6:11 AM Brian Goetz <brian.go...@oracle.com> wrote: > >> I really like how BiCollector can be private, so all we'd have to expose >> is toBoth(), and the arguments of toBoth() are just ordinary >> collectors. So no new types or abstractions; just a multiplexing. >> >> The use of Map.Entry as a pair surrogate is unfortunate -- its >> definitely not an Entry -- though I understand why you did this. I'm not >> sure if this is fatal or not for a JDK method, but it's pretty bad*. >> (You could generalize to n-ary, returning a List or array (you can take >> a varargs of Collector), at the loss of sharp types for the results.) >> >> >> *Yes, I'm sure one can find precedent of this being done; this has no >> effect on whether it's bad. >> >> On 6/11/2018 8:39 AM, Peter Levart 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 >> > >> >>