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

drJ*_*ava 5 java java-stream

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

我有一个值列表.

List1 = {10, -2, 23, 5, -11, 287, 5, -99}
Run Code Online (Sandbox Code Playgroud)

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

结果列表应该是这样的;

List1 = {10, 0, 23, 5, 0, 287, 5, 0}
List2 = {0, -2, 0, 0, -11, 0, 0, -99}
Run Code Online (Sandbox Code Playgroud)

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

Adr*_*ian 13

从 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
            ));
Run Code Online (Sandbox Code Playgroud)


Hol*_*ger 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));
Run Code Online (Sandbox Code Playgroud)


小智 7

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

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


Rob*_*per 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());
Run Code Online (Sandbox Code Playgroud)

如果您的列表是可修改的,则可以在一个流中进行所有操作.这并不比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);
   }}); 
Run Code Online (Sandbox Code Playgroud)


Tim*_*kle 6

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

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



当然你可以将问题分成两个独立的 FP 友好操作。

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

结论:

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

  • 我不明白为什么函数式编程应该仅限于将一个集合转换为另一个集合。从语义上讲,问题的两个列表与对的集合没有什么不同。只是内置支持较少,这使得解决方案更加复杂,[就像我的](/sf/answers/3150518381/),但与“Collectors.toList()”的作用没有根本区别,只是两个列表而不是一个。这并不比收集到一个列表中的功能更多或更少。 (2认同)
  • 我认为您对函数式编程的评论有些误导。您描述的“基本”模式也是错误的。模式为 f(x) = y。这里 x 和 y 可以是完全不同的类型。所以 x 可以是一个列表,y 可以是一个列表的列表。 (2认同)

sha*_*piy 6

每个解决方案都有优点和缺点。

\n\n
    \n
  • for循环是显而易见的答案,但您的问题明确\n提到了 Streams API。
  • \n
  • 使用不同的谓词 a) 导致代码重复,b) 容易出错,c) 导致额外的处理时间 \xe2\x80\x94 2N
  • \n
  • 定制Collector很难实现,给人一种多余工作的印象,而问题却显得那么简单,甚至是na\xc3\xafve。
  • \n
\n\n

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

\n\n
List<Integer> numbers = List.of(10, -2, 23, 5, -11, 287, 5, -99);\nMap<Boolean, List<Integer>> numbersByIsPositive = numbers.stream()\n    .collect(Collectors.groupingBy(number -> number >= 0));\n\nList<Integer> positiveNumbers = numbersByIsPositive.get(true);\nList<Integer> negativeNumbers = numbersByIsPositive.get(false);\n
Run Code Online (Sandbox Code Playgroud)\n\n

应用此方法时请考虑自动装箱和拆箱。

\n\n

输出:

\n\n
Positive numbers: [10, 23, 5, 287, 5]\nNegative numbers: [-2, -11, -99]\n
Run Code Online (Sandbox Code Playgroud)\n


Fed*_*ner 5

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

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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

针对您的具体问题,您可以使用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]
Run Code Online (Sandbox Code Playgroud)