【问题标题】:Split a list into sublists based on a condition with Stream api使用 Stream api 根据条件将列表拆分为子列表
【发布时间】:2017-12-13 20:58:36
【问题描述】:

我有一个具体的问题。有一些类似的问题,但这些问题要么是 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 解决这个问题?

【问题讨论】:

  • 您可以使用map 操作将它们分别映射到Math.max(0, n)Math.min(0, n)
  • 流需要两次迭代。最好使用单个老式循环。
  • @jsheeran 我也想到了 Map,但在我的情况下,这个操作将在 for 循环中执行,并且迭代次数将在运行时决定。我更喜欢少操作。

标签: java java-stream


【解决方案1】:
Map<Boolean, List<Integer>> results=
  List1.stream().collect(Collectors.partitioningBy( n -> n < 0));

我认为这个更漂亮且易于阅读。 (然后就可以从地图上得到否定和非否定列表了。)

【讨论】:

  • 但它不会产生零,这是 OP 想要的。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    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 循环好

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

    【讨论】:

    • 创建新列表时不能编辑流的原始列表吗?
    • 非常合适的答案!!
    • 你不能在迭代列表时修改它,你会出现 ConcurrentModificationException 因为如果列表发生变化,它将无法知道他必须迭代什么
    • @RobinTopper 我认为count() 跳过了Java 9 中的map() 操作。编辑:刚刚注意到Holger 就是这么说的。
    • 请注意,第二种方法,以其目前的形式,基本上等同于经典的for 循环。
    【解决方案4】:

    从 Java 12 开始,使用 Collectors::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
                ));
    

    【讨论】:

      【解决方案5】:

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

      函数式编程的基本模式是将 one 集合转换为 one 其他集合。这意味着您的要求不适合函数式方法,因此 Java 流是第二好的解决方案(在旧的 for(each) 循环之后)。


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

      它的缺点是需要对输入集合进行额外的循环。 对于小型集合(最多大约 100000 个项目),这可能不是问题,但对于较大的集合,您可能会引发性能问题。
      免责声明:不要选择或拒绝为性能原因,除非您已使用分析工具进行测量

      结论:

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

      【讨论】:

      • 嗯,有趣的一点。如果我想确定我的问题是否适合“功能方法”,您有什么建议?
      • “如果我想确定我的问题是否适合“功能方法”,你有什么建议?” 正如我在答案中所写:FP 适用于您有一个从一个集合到另一个集合的明确映射,而无需查看源集合或目标集合中的其他项目。
      • 我不明白为什么函数式编程应该仅限于将一个集合转换为另一个集合。从语义上讲,问题的两个列表与对的集合没有什么不同。 like mine 的内置支持较少,这使得解决方案更加复杂,但与 Collectors.toList() 所做的没有根本区别,它只是两个列表而不是一个。这与收集到一个列表中没有更多或更少的功能。
      • “我不明白为什么函数式编程应该局限于将一个集合转换为另一个集合。” - en.wikipedia.org/wiki/Functional_programming : “一种构建计算机程序的结构和元素,将计算视为对数学函数的评估,避免状态变化和可变数据。” 数学函数一个目标集合只要。但原因是:如果你有一把新锤子,任何问题看起来都像钉子......
      • 我认为您对函数式编程的评论有些误导。您描述的“基本”模式也是错误的。模式是 f(x) = y。这里 x 和 y 可以是完全不同的类型。所以 x 可以是一个列表,而 y 可以是一个列表。
      【解决方案6】:

      每种解决方案都各有利弊。

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

      我还没有看到其他人提到这一点,但是您可以在 Map&lt;Boolean,List&lt;Integer&gt;&gt; 映射中收集您的号码,其中 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);
      

      在应用这种方法时考虑自动装箱和拆箱。

      输出:

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

      【讨论】:

        【解决方案7】:
        嗯,你可以做到这一点:
          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));
        

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

        【讨论】:

          【解决方案8】:

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

          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]
          

          【讨论】:

            猜你喜欢
            • 2018-10-24
            • 2010-10-31
            • 2015-05-13
            • 2021-07-24
            • 1970-01-01
            • 1970-01-01
            • 2015-12-13
            • 1970-01-01
            • 2017-03-09
            相关资源
            最近更新 更多