BiCollector
Peter Levart
peter.levart at gmail.com
Thu Jun 14 07:46:31 UTC 2018
Hi Maurizio,
Collectors.partitioningBy is only superficially similar. It partitions
the stream into two disjunct sub-streams of elements and collects each
of them using the same collector.
BiCollector OTOH collects all the elemets of the original Stream "two
times" into two independent collections (in general using two different
collectors).
So result of partitioningBy and toBoth are usually totally different.
The features of toBoth allow constructing an equivalent case of
partitioningBy:
Stream<E> stream = ...;
Predicate<E> predicate = ...;
Collector<E> downstreamCollector = ...;
stream.collect(
partitioningBy(predicate, downstreamCollector)
);
gives equivalent results as:
stream.collect(
toBoth(
filtering(predicate, downstreamCollector),
filtering(predicate.negate, downstreamCollector)
)
}
But there's no way to use partitioningBy to simulate all the variants of
toBoth...
Regards, Peter
On 06/11/18 19:32, Maurizio Cimadamore wrote:
> Note also that this has some overlappings with
> Collectors.partitioningBy - which currently wraps results into a
> Map<Boolean, O>, where O is the desired collector output type. Without
> commenting on the feasibility of its inclusion in the JDK (Paul rules
> here :-)), I note that BiStream would obviously allow this
> functionality to be exposed in a more user friendly way.
>
> Cheers
> Maurizio
>
>
> On 11/06/18 13:39, 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