Hi Peter,

I am not concerned about performance of Map.Entry. I find its use awkward for 
similar reasons as Brian outlined. Tagir’s approach using a finisher nicely 
side steps this at the expense of another function. If in the future we have an 
officially blessed pair/2-tuple class we can overload the collector factory 
method.

Regarding BitStream. A Stream could be bisected into a BiStream, then merged 
back into a Stream or bi-collect (something which i did not get time to look 
at). Having gone back and looked at the prototype work I tend to see a 
bisecting collector as complementary in this respect since we have similar 
operations for collection as we do on Stream.

Paul. 

> On Jun 14, 2018, at 12:29 AM, Peter Levart <peter.lev...@gmail.com> wrote:
> 
> Hi Paul,
> 
> On 06/11/18 19:10, Paul Sandoz 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?
> 
> Well, I don't see the need to pack the two results into a Map.Entry (or any 
> similar) container as a drawback. It's not a performance drawback for sure, 
> because this is not happening on the stream-element scale, but on the final 
> result or intermediate accumulation results scale (the later only in parallel 
> non-CONCURRENT scenario). In non-parallel scenario, only a single (or two for 
> non-IDENTITY_FINISH) Map.Entry objects are created.
> 
> I also don't see a larger abstraction like BiStream as a natural fit for a 
> similar thing. As I understand BiStream attempts (maybe I haven't seen the 
> right ones?), they are more about passing pairs of elements down the 
> pipeline. BiCollector OTOH is "splitting" the single element pipeline at the 
> final collection stage, with the     purpose of constructing two independent 
> collections from a single pass of the original single-element Stream. This is 
> more about single pass than anything else. And single pass, I think, is 
> inevitably such that it can only execute a single collection strategy 
> (CONCURRENT vs. not CONCURRENT), regardless of the type of     the stream 
> (Stream vs. BiStream). Or have you prototyped a combined strategy in BiStream?
> 
> Regards, Peter
> 
>> 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 <peter.lev...@gmail.com> 
>>> <mailto:peter.lev...@gmail.com> 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
>>> 
> 

Reply via email to