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