如何验证Java 8 Stream中有两个特定元素?

时间:2017-08-10 22:23:15

标签: java java-8 java-stream

我们说我List<Car>并且我想搜索该列表以验证我同时拥有思域和焦点。如果它是OR,那么我很容易在.filter()上应用OR。请注意,对于此类AND,我无法filter().filter()

一个可行的解决方案是:

boolean hasCivic = reportElements.stream()
        .filter(car -> "Civic".equals(car.getModel()))
        .findFirst()
        .isPresent();

boolean hasFocus = reportElements.stream()
        .filter(car -> "Focus".equals(car.getModel()))
        .findFirst()
        .isPresent();

return hasCivic && hasFocus;

但是我基本上处理了两次列表。我无法在过滤器中应用&&,也无法filter().filter()

有没有办法处理流一次,以查找列表是否同时包含Civic和Focus车?

重要更新:提供的解决方案的关键问题是它们都保证O(n),而我的解决方案可以在两次比较后完成。如果我的汽车列表是1000万辆汽车,那么将会有非常显着的性能成本。然而,我的解决方案感觉不对,但也许这是性能最佳的解决方案......

3 个答案:

答案 0 :(得分:13)

您可以在"Civic" or "Focus"上过滤流,然后在getModel()上运行一个收藏家,返回Set<String>。然后你可以测试你的集合是否包含两个键。

Set<String> models = reportElements.stream()
       .map(Car::getModel)
       .filter(model -> model.equals("Focus") || model.equals("Civic"))
       .collect(Collectors.toSet());
return models.contains("Focus") && models.contains("Civic");

然而,这将处理整个流;它不会快速成功&#34;当两者都被发现时。

以下是&#34;快速成功&#34;短路方法。 (更新以包括评论和评论的说明,如下)

return reportElements.stream()
           .map(Car::getModel)
           .filter(model -> model.equals("Focus") || model.equals("Civic"))
           .distinct()
           .limit(2)
           .count() == 2;

一次打破一个流操作,我们有:

           .map(Car::getModel)

此操作将汽车流转换为汽车模型流。 我们这样做是为了提高效率 而不是在管道的其余部分中的多个位置多次调用car.getModel()(在filter(...)中两次以针对每个所需模型进行测试,并再次针对distinct()操作),我们应用此映射操作一次。 请注意,这不会创建&#34;临时地图&#34;在评论中提到; 它只是将汽车转换为汽车下一阶段管道的车型。

           .filter(model -> model.equals("Focus") || model.equals("Civic"))

这会过滤汽车模型流,只允许&#34; Focus&#34;和&#34; Civic&#34;汽车模型通过。

           .distinct()

此管道操作是stateful intermediate operation。 它会记住它在临时Set中看到的每个汽车模型。 (这可能是评论中提到的&#34;临时地图&#34;) 仅当临时集中不存在模型时, 它是否会(a)被添加到集合中,并且(b)被传递到管道的下一阶段。

在管道中的这一点,流中最多只能有两个元素:&#34; Focus&#34;或&#34; Civic&#34;或者两者都没有。 我们知道这一点,因为我们知道filter(...)只会传递这两个模型,我们知道distinct()会删除所有重复项。

但是,这个流管道本身并不知道。 它会继续将汽车对象传递到map阶段以转换为模型字符串,将这些模型传递到filter阶段,并将任何匹配的项目发送到distinct阶段。 它无法分辨这是徒劳的,因为它不能理解没有其他任何东西可以通过算法;它很简单地执行说明。

我们 明白了。 最多两个不同的模型可以通过distinct()阶段。 所以,我们遵循这个:

           .limit(2)

这是 short-circuiting stateful intermediate operation。 它保持通过的项目数量,和 在指示的数量之后,它终止流,导致所有后续项目被丢弃,甚至没有开始管道。

在管道中的这一点,流中最多只能有两个元素:&#34; Focus&#34;或&#34; Civic&#34;或者两者都没有。 但如果两者都有,那么流已经被截断并且最后了。

           .count() == 2;

计算通过管道的物品数量, 并根据所需的数字进行测试。

如果我们找到两个模型,流将立即终止,count()将返回2,并且将返回true。 如果两个模型都不存在,当然会处理流,直到痛苦结束,count()将返回小于2的值,并且false将会产生。

示例,使用无限的模型流。 每三个模型都是一个&#34; Civic&#34;,每个第7个模型都是&#34; Focus&#34;其余的都是&#34;模型#&#34;:

boolean matched = IntStream.iterate(1, i -> i + 1)
    .mapToObj(i -> i % 3 == 0 ? "Civic" : i % 7 == 0 ? "Focus" : "Model "+i)
    .peek(System.out::println)
    .filter(model -> model.equals("Civic") || model.equals("Focus"))
    .peek(model -> System.out.println("  After filter:   " + model))
    .distinct()
    .peek(model -> System.out.println("  After distinct: " + model))
    .limit(2)
    .peek(model -> System.out.println("  After limit:    " + model))
    .count() == 2;
System.out.println("Matched = "+matched);

输出:

Model 1
Model 2
Civic
  After filter:   Civic
  After distinct: Civic
  After limit:    Civic
Model 4
Model 5
Civic
  After filter:   Civic
Focus
  After filter:   Focus
  After distinct: Focus
  After limit:    Focus
Matched = true

请注意,3个模型已通过filter(),但只有2个模型超过了distinct()limit()。 更重要的是,请注意在达到无限模型流结束之前很久就返回了true

推广解决方案,因为OP需要可以与人,信用卡或IP地址等一起使用的东西,搜索条件可能不是两个固定的项目集:

Set<String> models = Set.of("Focus", "Civic");

return reportElements.stream()
           .map( Car::getModel )
           .filter( models::contains )
           .distinct()
           .limit( models.size() )
           .count() == models.size();

在这里,给定任意models组,可以获得任何特定车型的存在,而不仅限于2。

答案 1 :(得分:3)

你可以这样做:

reportElements.stream()
    .filter(car -> "Civic".equals(car.getModel()) || "Focus".equals(car.getModel()))
    .collect(Collectors.toMap(
            c -> c.getModel(),
            c -> c,
            (c1, c2) -> c1
    )).size() == 2;

甚至是Set

reportElements.stream()
    .filter(car -> "Civic".equals(car.getModel()) || "Focus".equals(car.getModel()))
    .map(car -> car.getModel())
    .collect(Collectors.toSet())
    .size() == 2;

distinct

reportElements.stream()
    .filter(car -> "Civic".equals(car.getModel()) || "Focus".equals(car.getModel()))
    .map(car -> car.getModel())
    .distinct()
    .count() == 2L;

答案 2 :(得分:1)

它“感觉不对”的原因是因为您强制流API执行它不想做的事情。使用传统循环几乎肯定会更好:

-fsigned-char