将List拆分为沿元素的子列表 [英] Splitting List into sublists along elements
问题描述
我有这个清单(清单< String>
):
["a", "b", null, "c", null, "d", "e"]
我想要这样的事情:
[["a", "b"], ["c"], ["d", "e"]]
换句话说我想要使用 null
值作为分隔符将我的列表拆分为子列表,以获取列表列表(列表< List< String>>
)。我在寻找Java 8解决方案。我尝试过使用 Collectors.partitioningBy
,但我不确定这是我在寻找什么。谢谢!
In other words I want to split my list in sublists using the null
value as separator, in order to obtain a list of lists (List<List<String>>
). I'm looking for a Java 8 solution. I've tried with Collectors.partitioningBy
but I'm not sure it is what I'm looking for. Thanks!
推荐答案
我现在提出的唯一解决方案是实现自己的自定义收集器。
The only solution I come up with for the moment is by implementing your own custom collector.
在阅读解决方案之前,我想添加一些关于此的说明。我把这个问题更多地作为编程练习,我不确定是否可以使用并行流来完成。
Before reading the solution, I want to add a few notes about this. I took this question more as a programming exercise, I'm not sure if it can be done with a parallel stream.
因此,如果管道在并行中运行,您必须意识到默默地。
So you have to be aware that it'll silently break if the pipeline is run in parallel.
不是理想的行为,应该避免。这就是我在合并器部分抛出异常的原因(而不是(l1,l2) - > {l1.addAll(l2); return l1;}
),as as它在组合两个列表时并行使用,因此您有一个例外而不是错误的结果。
This is not a desirable behavior and should be avoided. This is why I throw an exception in the combiner part (instead of (l1, l2) -> {l1.addAll(l2); return l1;}
), as it's used in parallel when combining the two lists, so that you have an exception instead of a wrong result.
由于列表复制,这也不是很有效(尽管它使用本机方法复制底层数组。)
Also this is not very efficient due to list copying (although it uses a native method to copy the underlying array).
所以这里是收集器实现:
So here's the collector implementation:
private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
final List<String> current = new ArrayList<>();
return Collector.of(() -> new ArrayList<List<String>>(),
(l, elem) -> {
if (sep.test(elem)) {
l.add(new ArrayList<>(current));
current.clear();
}
else {
current.add(elem);
}
},
(l1, l2) -> {
throw new RuntimeException("Should not run this in parallel");
},
l -> {
if (current.size() != 0) {
l.add(current);
return l;
}
);
}
以及如何使用它:
List<List<String>> ll = list.stream().collect(splitBySeparator(Objects::isNull));
输出:
[[a, b], [c], [d, e]]
由于 Joop Eggen的答案已经结束,它似乎可以在平行(给他信任!)。通过它,它将自定义收集器实现减少到:
As the answer of Joop Eggen is out, it appears that it can be done in parallel (give him credit for that!). With that it reduces the custom collector implementation to:
private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
return Collector.of(() -> new ArrayList<List<String>>(Arrays.asList(new ArrayList<>())),
(l, elem) -> {if(sep.test(elem)){l.add(new ArrayList<>());} else l.get(l.size()-1).add(elem);},
(l1, l2) -> {l1.get(l1.size() - 1).addAll(l2.remove(0)); l1.addAll(l2); return l1;});
}
这让关于并行性的段落有点过时了,不过我让它为它可以很好的提醒。
which let the paragraph about parallelism a bit obsolete, however I let it as it can be a good reminder.
请注意,Stream API并不总是替代品。有些任务使用流更容易和更合适,而有些任务则不然。在您的情况下,您还可以为此创建实用程序方法:
Note that the Stream API is not always a substitute. There are tasks that are easier and more suitable using the streams and there are tasks that are not. In your case, you could also create a utility method for that:
private static <T> List<List<T>> splitBySeparator(List<T> list, Predicate<? super T> predicate) {
final List<List<T>> finalList = new ArrayList<>();
int fromIndex = 0;
int toIndex = 0;
for(T elem : list) {
if(predicate.test(elem)) {
finalList.add(list.subList(fromIndex, toIndex));
fromIndex = toIndex + 1;
}
toIndex++;
}
if(fromIndex != toIndex) {
finalList.add(list.subList(fromIndex, toIndex));
}
return finalList;
}
并将其称为 List< List< String> > list = splitBySeparator(originalList,Objects :: isNull);
。
可以改进检查边缘情况。
It can be improved for checking edge-cases.
这篇关于将List拆分为沿元素的子列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!