沿元素将列表拆分为子列表 [英] Splitting List into sublists along elements

查看:26
本文介绍了沿元素将列表拆分为子列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个列表(List):

["a", "b", null, "c", null, "d", "e"]

我想要这样的东西:

[["a", "b"], ["c"], ["d", "e"]]

换句话说,我想使用 null 值作为分隔符将我的列表拆分为子列表,以获得列表列表 (List>代码>).我正在寻找 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;}),因为它在组合时并行使用两个列表,让你有一个异常而不是一个错误的结果.

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).

这里是收集器的实现:

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 = splitBySeparator(originalList, Objects::isNull);.

可以改进检查边缘情况.

It can be improved for checking edge-cases.

这篇关于沿元素将列表拆分为子列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆