java 使用Stream api根据条件将列表拆分为子列表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45006727/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 08:27:17  来源:igfitidea点击:

Split a list into sublists based on a condition with Stream api

javajava-stream

提问by drJava

I have a specific question. There are some similar questions but these are either with Python, not with Java, or the requirements are different even if the question sounds similar.

我有一个具体的问题。有一些类似的问题,但这些问题要么是用 Python 写的,要么是用 Java 写的,或者即使问题听起来很相似,要求也不同。

I have a list of values.

我有一个值列表。

List1 = {10, -2, 23, 5, -11, 287, 5, -99}

At the end of the day, I would like to split lists based on their values. I mean if the value is bigger than zero, it will be stay in the original list and the corresponding index in the negative values list will be set zero. If the value is smaller than zero, it will go to the negative values list and the negative values in the original list will be replaced with zero.

在一天结束时,我想根据它们的值拆分列表。我的意思是如果该值大于零,它将保留在原始列表中,而负值列表中的相应索引将设置为零。如果该值小于零,它将转到负值列表,原始列表中的负值将被替换为零。

The resulting lists should be like that;

结果列表应该是这样的;

List1 = {10, 0, 23, 5, 0, 287, 5, 0}
List2 = {0, -2, 0, 0, -11, 0, 0, -99}

Is there any way to solve this with Stream api in Java?

有什么办法可以用 Java 中的 Stream api 解决这个问题吗?

采纳答案by Holger

If you want to do it in a single Stream operation, you need a custom collector:

如果要在单个 Stream 操作中完成,则需要自定义收集器:

List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);

List<List<Integer>> result = list.stream().collect(
    () -> Arrays.asList(new ArrayList<>(), new ArrayList<>()),
    (l,i) -> { l.get(0).add(Math.max(0, i)); l.get(1).add(Math.min(0, i)); },
    (a,b) -> { a.get(0).addAll(b.get(0)); a.get(1).addAll(b.get(1)); });

System.out.println(result.get(0));
System.out.println(result.get(1));

回答by Tamas

Map<Boolean, List<Integer>> results=
  List1.stream().collect(Collectors.partitioningBy( n -> n < 0));

I think that this one is prettier and easy to read. (You can then get the negative and non-negative list from the map.)

我认为这个更漂亮,更容易阅读。(然后您可以从地图中获取否定和非否定列表。)

回答by Robin Topper

As shmosel already pointed out in the comments, you'll need two iterations using streams:

正如 shmosel 在评论中已经指出的那样,您需要使用流进行两次迭代:

List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
List<Integer> positives = list.stream().map(i -> i < 0 ? 0 : i).collect(Collectors.toList());
List<Integer> negatives = list.stream().map(i -> i < 0 ? i : 0).collect(Collectors.toList());


All in one stream is possible if your list is modifiable. This is not better than a for-loop

如果您的列表是可修改的,那么多合一流是可能的。这并不比 for 循环好

List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
List<Integer> list2 = new ArrayList<>();

IntStream.range(0, list.size()).forEach(i -> {
   int j;
   if ((j = list.get(i)) < 0) {
       list2.add(j);
       list.set(i, 0);
   } else {
       list2.add(0);
   }}); 

回答by Timothy Truckle

Java-Streams are a functional programmingfeature.

Java-Streams 是一种函数式编程特性。

The essential pattern of functional programming is that you convert onecollection to oneother collection. This means your requirement is does not suit to a functional approach and hence java streams are the second best solution (after legacy for(each) loop).

函数式编程的基本模式是,你转换一个集合到一个其他集合。这意味着您的要求不适合函数式方法,因此 java 流是第二好的解决方案(在遗留 for(each) 循环之后)。



But
Of cause you can split the problem into two separate FP friendly operations.


当然,您可以将问题拆分为两个独立的 FP 友好操作。

The downside it that this requires an additional loop over the input collection. For small collections (up to roughly 100000 items) this may not be a problem but for bigger collections you may raise a performance issue.
Disclaimer:do not choose or deny an approach for performance reasons unless you have justified your decision by measurement with a profiling tool!

不利的一面是,这需要对输入集合进行额外的循环。对于小型集合(最多大约 100000 个项目),这可能不是问题,但对于较大的集合,您可能会引发性能问题。
免责声明:不要出于性能原因选择或拒绝一种方法,除非您已通过使用分析工具进行测量来证明您的决定是合理的!

conclusion:

结论:

I'd consider the "legacy loop" the better approach since it may be more readable in the sense that it better expresses your intent (to split up the collection).

我认为“传统循环”是更好的方法,因为它可能更具可读性,因为它可以更好地表达您的意图(拆分集合)。

回答by Federico Peralta Schaffner

A generic solution without streams might consist of choosing between two possible consumers, based on a condition:

没有流的通用解决方案可能包括根据条件在两个可能的使用者之间进行选择:

private static <T> Consumer<T> splitBy(
        Predicate<T> condition,
        Consumer<T> action1,
        Consumer<T> action2,
        T zero) {
    return n -> {
        if (condition.test(n)) {
            action1.accept(n);
            action2.accept(zero);
        } else {
            action1.accept(zero);
            action2.accept(n);
        }
    };
}

For your specific problem, you could use the splitBymethod as follows:

对于您的具体问题,您可以使用以下splitBy方法:

List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);

List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

list.forEach(splitBy(n -> n > 0, list1::add, list2::add, 0));

System.out.println(list1); // [10, 0, 23, 5, 0, 287, 5, 0]
System.out.println(list2); // [0, -2, 0, 0, -11, 0, 0, -99]

回答by Eugene

Well you could do that in place:

那么你可以就地做到这一点:

  List<Integer> left = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
    int[] right = new int[left.size()];

    IntStream.range(0, left.size())
            .filter(i -> left.get(i) < 0)
            .forEach(x -> {
                right[x] = left.get(x);
                left.set(x, 0);
            });
    System.out.println(left);
    System.out.println(Arrays.toString(right));

That is a side-effect, but as far as I can tell, it is a safe side-effect.

这是一种副作用,但据我所知,这是一种安全的副作用。

回答by Adrian

In Java 12 it can be done very simple by using teeingcollector:

在 Java 12 中,它可以通过使用teeing收集器非常简单地完成:

var divided = List.of(10, -2, 23, 5, -11, 287, 5, -99)
            .stream()
            .collect(Collectors.teeing(
                    Collectors.mapping(i -> Math.max(0, i), Collectors.toList()),
                    Collectors.mapping(i -> Math.min(0, i), Collectors.toList()),
                    List::of
            ));

回答by shapiy

There are pros and cons in each solution.

每个解决方案都有利有弊。

  • forloop is the obvious answer, but your question explicitly mentions Streams API.
  • Using different predicates a) causes code duplication, b) is error prone, c) results in additional processing time — 2N
  • Custom Collectoris difficult to implement, and gives the impression of redundant work while the problem seems so straightforward, even na?ve.
  • forloop 是显而易见的答案,但您的问题明确提到了 Streams API。
  • 使用不同的谓词 a) 导致代码重复,b) 容易出错,c) 导致额外的处理时间 — 2N
  • 自定义Collector很难实现,给人的印象是多余的工作,而问题似乎很简单,甚至很幼稚。

I haven't seen anyone else mentioning this, but you can collect your numbers in a Map<Boolean,List<Integer>>map, where key corresponds to your grouping criterion, and Listis the selection of items matching the criterion, for example:

我还没有看到其他人提到这一点,但是您可以在Map<Boolean,List<Integer>>地图中收集您的数字,其中 key 对应于您的分组标准,并且List是匹配该标准的项目的选择,例如:

List<Integer> numbers = List.of(10, -2, 23, 5, -11, 287, 5, -99);
Map<Boolean, List<Integer>> numbersByIsPositive = numbers.stream()
    .collect(Collectors.groupingBy(number -> number >= 0));

List<Integer> positiveNumbers = numbersByIsPositive.get(true);
List<Integer> negativeNumbers = numbersByIsPositive.get(false);

Think of auto-boxing and -unboxing when applying this approach.

在应用这种方法时,请考虑自动装箱和取消装箱。

Output:

输出:

Positive numbers: [10, 23, 5, 287, 5]
Negative numbers: [-2, -11, -99]