BiCollector

Stephen Colebourne scolebourne at joda.org
Thu Jun 14 12:56:14 UTC 2018


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 <amaembo at 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.goetz at 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
>> >
>>
>>


More information about the core-libs-dev mailing list