根据Stream api的条件将列表拆分为子列表

时间:2017-07-10 08:03:33

标签: java java-stream

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

我有一个值列表。

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

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

结果列表应该是这样的;

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

有没有办法用Java中的Stream api解决这个问题?

8 个答案:

答案 0 :(得分:7)

如果您想在单个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));

答案 1 :(得分:6)

正如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());

如果您的列表可以修改,则可以在一个流中进行所有操作。这并不比for-loop

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

答案 2 :(得分:4)

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

我认为这是一个更漂亮且易于阅读的东西。 (然后,您可以从地图中获得否定列表和非否定列表。)

答案 3 :(得分:2)

那么你可以做到这一点:

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

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

答案 4 :(得分:2)

Java-Streams是函数式编程功能。

函数式编程的基本模式是将一个集合转换为一个其他集合。这意味着您的要求不适合功能方法,因此Java流是第二个最佳解决方案(在(每个)循环遗留之后)。

<强>但是
因为您可以将问题分成两个独立的FP友好操作。

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

的结论:

我认为“遗留循环”是一种更好的方法,因为它可以更好地表达你的意图(分割集合)。

答案 5 :(得分:2)

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

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

对于您的具体问题,您可以使用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]

答案 6 :(得分:0)

每种解决方案各有利弊。

  • for循环是显而易见的答案,但您的问题明确 提到Streams API。
  • 使用不同的谓词a)导致代码重复,b)容易出错,c)导致额外的处理时间-2N
  • 自定义Collector难以实现,给人留下多余工作的印象,而问题似乎是如此简单明了,甚至是幼稚的。

我没有看到其他人提及此内容,但是您可以在Map<Boolean,List<Integer>>映射中收集数字,其中键对应于您的分组条件,而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);

使用这种方法时,请考虑自动装箱和-unboxing。

输出:

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

答案 7 :(得分:0)

在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
            ));