是否可以在不创建中间映射的情况下从按收集器分组对每个列表进行操作



我有以下代码,它对列表进行分组,然后对每个分组的列表进行操作,依次将其转换为单个项目:

Map<Integer, List<Record>> recordsGroupedById = myList.stream()
    .collect(Collectors.groupingBy(r -> r.get("complex_id")));
List<Complex> whatIwant = recordsGroupedById.values().stream().map(this::toComplex)
    .collect(Collectors.toList());

toComplex函数如下所示:

Complex toComplex(List<Record> records);

我觉得我可以在不创建中间地图的情况下做到这一点,也许使用 reduce。 有什么想法吗?

输入流按我

想要在流中按顺序分组的元素进行排序。 在正常的循环结构中,我将能够确定下一组何时开始并在那时创建一个"复杂"。

创建一个将 groupingBy 和后处理函数与 collectingAndThen 相结合的收集器。

Map<Integer, Complex> map = myList.stream()
    .collect(collectingAndThen(groupingBy(r -> r.get("complex_id"), 
                               Xxx::toComplex));

如果您只想在这里Collection<Complex>,则可以向地图询问其values()

好吧,你可以避免Map(老实说!)并使用我的StreamEx库在单个管道中完成所有操作:

List<Complex> result = StreamEx.of(myList)
        .sortedBy(r -> r.get("complex_id"))
        .groupRuns((r1, r2) -> r1.get("complex_id").equals(r2.get("complex_id")))
        .map(this::toComplex)
        .toList();

在这里,我们首先按complex_id对输入进行排序,然后使用自定义中间操作groupRuns如果应用于两个相邻元素的给定BiPredicate返回 true,则将相邻的流元素分组到List。然后,您有一个列表流,该列表流映射到Complex对象流,最后收集到列表中。

实际上没有中间映射,groupRuns实际上是懒惰的(在顺序模式下,它一次只保留一个中间List),它也很好地并行化。另一方面,我的测试表明,对于未排序的输入,这种解决方案比基于groupingBy的解决方案慢因为它涉及对整个输入进行排序。当然,sortedBy(这只是sorted(Comparator.comparing(...))的快捷方式)需要中间内存来存储输入。如果您的输入已经排序(或至少部分排序,因此 TimSort 可以快速执行),那么这样的解决方案通常比 groupingBy 更快。

不,你不能。在向前移动之前,必须收集所有数据以确保所有组的内容都是已知的。但是,显然,如果您可以在分配给组时对组中的每个元素执行流程,则可以做到这一点。

这样想 - 想象一下列表中的第一项和列表中的最后一项包含相同的complex_id。然后,您必须等待列表的末尾才能完全收集该组(以及所有其他组),因此您必须在处理之前将所有组聚集在一起。

另外 - 你显然应该能够做到:

    List<Complex> whatIwant = myList.stream()
            .collect(Collectors.groupingBy(r -> r.get("complex_id")))
            .values()
            .stream()
            .map(this::toComplex)
            .collect(Collectors.toList());

最新更新