The task you want to achieve is very different from what the group does. groupingBy does not depend on the order of Stream s elements, but on the Map s algorithm applied to the result of the Function classifier.
You want to photograph adjacent items that share a property value into one List element. There is also no need to have a Stream sorted by this property, as long as you can guarantee that all elements that have the same property value are grouped.
It may be possible to formulate this task as a reduction, but for me the resulting structure looks too complicated.
So, if direct support for this function is not added to Stream s, an iterator-based approach looks the most pragmatic for me:
class Folding<T,G> implements Spliterator<Map.Entry<G,List<T>>> { static <T,G> Stream<Map.Entry<G,List<T>>> foldBy( Stream<? extends T> s, Function<? super T, ? extends G> f) { return StreamSupport.stream(new Folding<>(s.spliterator(), f), false); } private final Spliterator<? extends T> source; private final Function<? super T, ? extends G> pf; private final Consumer<T> c=this::addItem; private List<T> pending, result; private G pendingGroup, resultGroup; Folding(Spliterator<? extends T> s, Function<? super T, ? extends G> f) { source=s; pf=f; } private void addItem(T item) { G group=pf.apply(item); if(pending==null) pending=new ArrayList<>(); else if(!pending.isEmpty()) { if(!Objects.equals(group, pendingGroup)) { if(pending.size()==1) result=Collections.singletonList(pending.remove(0)); else { result=pending; pending=new ArrayList<>(); } resultGroup=pendingGroup; } } pendingGroup=group; pending.add(item); } public boolean tryAdvance(Consumer<? super Map.Entry<G, List<T>>> action) { while(source.tryAdvance(c)) { if(result!=null) { action.accept(entry(resultGroup, result)); result=null; return true; } } if(pending!=null) { action.accept(entry(pendingGroup, pending)); pending=null; return true; } return false; } private Map.Entry<G,List<T>> entry(G g, List<T> l) { return new AbstractMap.SimpleImmutableEntry<>(g, l); } public int characteristics() { return 0; } public long estimateSize() { return Long.MAX_VALUE; } public Spliterator<Map.Entry<G, List<T>>> trySplit() { return null; } }
The flattering nature of the resulting folded Stream can best be demonstrated by applying it to an infinite stream:
Folding.foldBy(Stream.iterate(0, i->i+1), i->i>>4) .filter(e -> e.getKey()>5) .findFirst().ifPresent(e -> System.out.println(e.getValue()));
Holger Sep 04 '14 at 10:40 2014-09-04 10:40
source share