是否有可能在java 8中执行一个懒惰的groupby,返回一个流?

时间:2014-09-03 20:21:09

标签: java lazy-loading java-8 java-stream

我想要通过对其行进行分组来处理一些大型文本文件。

我尝试使用新的流媒体功能,例如

return FileUtils.readLines(...) 
            .parallelStream()
            .map(...)
            .collect(groupingBy(pair -> pair[0]));

问题是,AFAIK,这会生成一个Map。

有没有办法像上面那样生成高级代码,例如,条目流?

更新:我正在寻找的是像python的itertools.groupby。我的文件已经排序(通过pair [0]),我只想逐个加载组。

我已经有了一个迭代解决方案。我只是想知道是否有更多的声明方式来做到这一点。顺便说一句,使用番石榴或其他第三方图书馆不会是一个大问题。

3 个答案:

答案 0 :(得分:3)

您想要实现的任务与分组完成的任务完全不同。 groupingBy不依赖于Stream元素的顺序,而是依赖于应用于分类器Map的结果的Function算法。

您想要的是将具有共同属性值的相邻项目折叠到一个List项目中。只要您可以保证具有相同属性值的所有项都被聚集在一起,甚至不必将Stream按该属性排序。

也许有可能将此任务表述为减少,但对我来说,结果结构看起来太复杂了。

因此,除非直接支持此功能添加到Stream,否则基于迭代器的方法对我来说看起来最实用:

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; }
}

通过将折叠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())); 的惰性:

{{1}}

答案 1 :(得分:1)

cyclops-react,我为图书馆做出了贡献,提供了sharding和可能做你想做的事情的分组功能。

  ReactiveSeq<ListX<TYPE>> grouped = ReactiveSeq.fromCollection(FileUtils.readLines(...) )
             .groupedStatefullyWhile((batch,next) ->  batch.size()==0 ? true : next.equals(batch.get(0)));

groupedStatefullyWhile运算符允许根据批处理的当前状态对元素进行分组。 ReactiveSeq是单线程顺序流。

  Map<Key, Stream<Value> sharded = 
                  new LazyReact()
                 .fromCollection(FileUtils.readLines(...) )
                 .map(..)
                 .shard(shards, pair -> pair[0]);

这将创建一个LazyFutureStream(实现java.util.stream.Stream),它将异步并行地处理文件中的数据。它是懒惰的,在数据​​通过之前不会开始处理。

唯一需要注意的是,您需要事先定义分片。即上面的'shards'参数是一个async.Queue的Map,它是由分片的键控制的(可能是对[0]是什么?)。

e.g。

Map<Integer,Queue<String>> shards;

There is a sharding example with video heretest code here

答案 2 :(得分:0)

可以collapse使用StreamEx

来完成
final int[][] aa = { { 1, 1 }, { 1, 2 }, { 2, 2 }, { 2, 3 }, { 3, 3 }, { 4, 4 } };

StreamEx.of(aa)
        .collapse((a, b) -> a[0] == b[0], Collectors.groupingBy(a -> a[0]))
        .forEach(System.out::println);

我们可以添加peeklimit来验证它是否是惰性计算:

StreamEx.of(aa)
        .peek(System.out::println)
        .collapse((a, b) -> a[0] == b[0], Collectors.groupingBy(a -> a[0]))
        .limit(1)
        .forEach(System.out::println);