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

Ste*_*ier 17 java java-8 java-stream

假设我已经List<Car>并且我想搜索该列表以验证我同时拥有思域和焦点.如果它是一个OR,它很容易,因为我可以在它上面应用OR .filter().请记住,我不能filter().filter()为这种类型的AND做.

一个有效的解决方案是:

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

但后来我基本上处理了两次列表.我不能&&在过滤器中应用,也不能filter().filter().

有没有办法处理流一次,以查找列表是否包含思域和焦点汽车?

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

AJN*_*eld 18

您可以过滤流"Civic" or "Focus",然后在getModel()返回a时运行收集器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");
Run Code Online (Sandbox Code Playgroud)

但是,这将处理整个流; 当两者都被发现时,它不会"快速成功".


以下是"快速成功"的短路方法.(更新以包括评论和评论的澄清,如下)

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

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

           .map(Car::getModel)
Run Code Online (Sandbox Code Playgroud)

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

           .filter(model -> model.equals("Focus") || model.equals("Civic"))
Run Code Online (Sandbox Code Playgroud)

这会过滤汽车模型流,只允许"焦点"和"思域"汽车模型通过.

           .distinct()
Run Code Online (Sandbox Code Playgroud)

此管道操作是有状态的中间操作.它会记住它在临时看到的每个汽车模型Set.(这可能是评论中提到的"临时地图".)只有在临时集中不存在模型时,它才会被(a)添加到集合中,并且(b)传递到下一阶段管道.

在流水线的这一点上,流中最多只能有两个元素:"Focus"或"Civic",或者两者都没有.我们知道这一点,因为我们知道filter(...)只会通过这两个模型,我们知道这distinct()将删除任何重复.

但是,这个流管道本身并不知道.它将继续将汽车对象传递到map舞台以转换为模型字符串,将这些模型传递到filter舞台,并将任何匹配的项目发送到distinct舞台.它不能说这是徒劳的,因为它不明白没有别的东西可以通过算法; 它很简单地执行指令.

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

           .limit(2)
Run Code Online (Sandbox Code Playgroud)

这是一种短路状态中间操作.它保持通过的项目数的计数,并且在指示的数量之后,它终止流,导致所有后续项目被丢弃,甚至没有开始管道.

在流水线的这一点上,流中最多只能有两个元素:"Focus"或"Civic",或者两者都没有.但如果两者都有,那么流已被截断并且结束了.

           .count() == 2;
Run Code Online (Sandbox Code Playgroud)

计算通过管道的项目数量,并根据所需的数量进行测试.

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


例如,使用无限的模型流.每个第三个模型都是"思域",每个第7个模型都是"焦点",其余的都是"模型#":

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

输出:

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

请注意,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();
Run Code Online (Sandbox Code Playgroud)

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

  • @ 123-xyz只是试图变得花哨/懒惰.`.filter(model - > model.equals("Focus")|| model.equals("Civic"))`会工作,可能更快.但是要搜索更多的项目(例如10个特定模型),临时集和`contains`应该更快. (5认同)
  • @ByeBye不,在`distinct()`之后,有一个可能无限的流只包含通过过滤器的不同项.使用`limit(2)`,当流发出第二个项时,流的处理停止.这使得短路快速成功. (3认同)