【问题标题】:Filter on original stream if previous filter resulted in an empty stream如果先前的过滤器导致空流,则过滤原始流
【发布时间】:2021-01-05 17:08:17
【问题描述】:

如果前一个过滤器返回一个空流,希望想出一种方法来对原始流应用过滤器。例如:

  • 给定一个数字流
  • 获取并返回所有偶数
  • 如果没有,获取并返回所有是 3 的倍数的数字
  • 如果没有,则获取并返回所有为 5 的倍数的数字
  • 等等..

一个简单的方法是:

List<Integer> numbers = ...

List<Integer> filteredNums = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

if (filteredNums.isEmpty()) {
    filteredNums = numbers.stream()
        .filter(n -> n % 3 == 0)
        .collect(Collectors.toList());
}

if (filteredNums.isEmpty()) {
    filteredNums = numbers.stream()
        .filter(n -> n % 5 == 0)
        .collect(Collectors.toList());
}

给定一个数字列表,例如 1、3、5、15、21,将得到 3、15、21。

我的数字示例是我正在尝试做的简化版本,但传达了相同的概念。我可以创建一个收集器来执行此操作,但总的来说,我觉得我缺少一些简单的东西。理想情况下,我希望该解决方案能够在流中使用,以便我可以继续对其执行其他流操作。在这种情况下,也许我想将其减少为一个总和或映射,其中我将每个数字相乘并将收集到一个列表中。

【问题讨论】:

    标签: java java-stream


    【解决方案1】:

    您可以拥有一个带有谓词的流,并使用每个谓词过滤您的集合,直到过滤操作的结果返回一个非空列表:

    Stream<Predicate<Integer>> predicates = Stream.of(n -> n % 2 == 0,
                                                      n -> n % 3 == 0,
                                                      n -> n % 5 == 0);
    
    List<Integer> filteredNums = predicates.map(
            predicate -> numbers.filter(predicate)
                                .collect(Collectors.toList()))
        .dropWhile(Collection::isEmpty)
        .findFirst()
        .orElse(Collections.emptyList());
    

    这会将每个谓词转换为过滤列表(通过将当前谓词应用于原始列表)。过滤后的空列表被丢弃,直到谓词产生非空列表或谓词都被消耗掉。

    【讨论】:

    • 不错的解决方案!为什么不直接标记.orElse(Collections.emptyList())findFirst() 并返回实际的List
    • @WJS 当然,为什么不呢?感谢您的建议
    • 我想知道什么更好,在主流中流一次并收集像你一样的每个,或者像 YCF_L 的解决方案一样流两次并收集一次。同样重要的是要注意 dropWhile 来自 Java 9。
    • 我觉得有趣的是,当我们已经达到 Java 14 时,这么多人警告 Java 9 甚至 11 的特性,就好像它是新事物一样。但我不记得同样级别的警告Java 1.5 发布后的泛型。
    • 嗯,从 Java 8 开始有一个很大的不同,发布的频率更高,直到 11 年左右才引入主要的新功能,所以人们会跳过发布。此外,许多大公司的采用速度较慢,可能只支持某个版本。
    【解决方案2】:

    您可以通过以下方式组合条件:

    List<Integer> divs = Arrays.asList(2, 3, 5);
    List<Integer> filteredNums = divs.stream()
            .filter(d -> numbers.stream().anyMatch(n -> n % d == 0))
            .findFirst()
            .map(d -> numbers.stream().filter(n -> n % d == 0).collect(Collectors.toList()))
            .orElse(Collections.EMPTY_LIST);
    

    这个想法是创建一个包含您要使用的分隔符的列表,然后找到第一个分隔符(如果存在)然后过滤所有数字或返回一个空列表

    【讨论】:

    • 肯定会适用于我的具体示例,尽管我应该在最初的问题中更清楚。我只是用那个例子来保持简单。过滤谓词是变化的。我的实际用例是我正在流式传输对象并查找与特定过滤器匹配的所有对象,如果没有找到,则进入下一个过滤器。我想我可以做一些类似于你所做的事情,但顶部的列表可能是谓词列表。
    • @SteveW 这是正确的,您可以在原始问题中传递谓词列表。
    • 也许这可以改进为不为每个除数两次流式传输数字
    • @fps 如果只能有一个流和一个收集,那将是理想的。这个答案和你提供的答案是迄今为止最好的。乍一看,两者都不是特别容易理解,但它们回答了问题并且很容易扩展到我提供的数字示例之外。
    【解决方案3】:
    List<Predicate<Integer>> predicates = Arrays.asList(n -> n % 2 == 0,
                n -> n % 3 == 0,
                n -> n % 5 == 0);
    
        List<Integer> numbers = Arrays.asList(11,12,15,4,43);
    
        List<Integer> integers = predicates.stream()
                .map(p -> numbers.stream().filter(n -> p.test(n)).collect(Collectors.toList()))
                .filter(l -> !l.isEmpty())
                .findFirst().orElse(Collections.emptyList());
    

    【讨论】:

      猜你喜欢
      • 2020-10-07
      • 1970-01-01
      • 2022-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-13
      • 1970-01-01
      相关资源
      最近更新 更多