简体   繁体   English

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

[英]How to validate that a Java 8 Stream has two specific elements in it?

Let's say I have List<Car> and I want to search through that list to verify that I have both a Civic AND a Focus. 假设我有List<Car> ,我想搜索该列表以验证我同时拥有Civic和Focus。 If it's an OR it's very easy in that I can just apply an OR on the .filter() . 如果它是一个OR,那么我就可以在.filter()上应用OR。 Keep in mind that I can't do filter().filter() for this type of AND. 请记住,我不能为这种类型的AND做filter().filter()

A working solution would be to do: 一个有效的解决方案是:

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;

But then I'm basically processing the list twice. 但后来我基本上处理了两次列表。 I can't apply an && in the filter nor can I do filter().filter() . 我不能在过滤器中应用&&也不能使用filter().filter()

Is there a way to process the stream once to find if the list contains both a Civic and a Focus car? 有没有办法处理流一次,以查找列表是否包含思域和焦点汽车?

IMPORTANT UPDATE: The key problem with the solutions provided is that they all guarantee O(n) whereas my solution could be done after just two comparisons. 重要更新:提供的解决方案的关键问题是它们都保证O(n),而我的解决方案可以在两次比较后完成。 If my list of cars is say 10 million cars then there would be a very significant performance cost. 如果我的汽车列表是1000万辆汽车,那么将会有非常显着的性能成本。 My solution however doesn't feel right, but maybe it is the best solution performance wise... 然而,我的解决方案感觉不对,但也许这是性能最佳的解决方案......

You could filter the stream on "Civic" or "Focus" , and then run a collector on getModel() returning a Set<String> . 您可以在"Civic" or "Focus"上过滤流,然后在getModel()上运行一个收集器,返回Set<String> Then you could test if your set contains both keys. 然后你可以测试你的集合是否包含两个键。

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

However, this would process the entire stream; 但是,这将处理整个流; it wouldn't "fast succeed" when both have been found. 当两者都被发现时,它不会“快速成功”。


The following is a "fast succeed" short-circuiting method. 以下是“快速成功”的短路方法。 (Updated to include comments and clarifications from comments, below) (更新以包括评论和评论的澄清,如下)

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

Breaking the stream operations down one at a time, we have: 我们一次打破一个流操作,我们有:

           .map(Car::getModel)

This operation transforms the stream of cars into a stream of car models. 该操作将汽车流转换为汽车模型流。 We do this for efficiency. 我们这样做是为了提高效率 Instead of calling car.getModel() multiple times in various places in the remainder of the pipeline (twice in the filter(...) to test against each of the desired models, and again for the distinct() operation), we apply this mapping operation once. 不是在管道的其余部分中的多个位置多次调用car.getModel() (在filter(...)两次以针对每个所需模型进行测试,并再次针对distinct()操作),我们应用这个映射操作一次。 Note that this does not create the "temporary map" mentioned in the comments; 请注意,这不会创建评论中提到的“临时地图”; it merely translates the car into the car's model for the next stage of the pipeline. 它只是将汽车转换为汽车下一阶段管道的模型。

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

This filters the stream of car models, allowing only the "Focus" and "Civic" car models to pass. 这会过滤汽车模型流,只允许“焦点”和“思域”汽车模型通过。

           .distinct()

This pipeline operation is a stateful intermediate operation . 此管道操作是有状态的中间操作 It remembers each car model that it sees in a temporary Set . 它会记住它在临时Set看到的每个汽车模型。 (This is likely the "temporary map" mentioned in the comments.) Only if the model does not exist in the temporary set, will it be (a) added to the set, and (b) passed on to the next stage of the pipeline. (这可能是评论中提到的“临时地图”。)只有在临时集中不存在模型时,它才会被(a)添加到集合中,并且(b)传递到下一阶段管道。

At this point in the pipeline, there can only be at most two elements in the stream: "Focus" or "Civic" or neither or both. 在流水线的这一点上,流中最多只能有两个元素:“Focus”或“Civic”,或者两者都没有。 We know this because we know the filter(...) will only ever pass those two models, and we know that distinct() will remove any duplicates. 我们知道这是因为我们知道filter(...)只会传递这两个模型,我们知道distinct()会删除任何重复项。

However, this stream pipeline itself does not know that. 但是,这个流管道本身并不知道。 It would continue to pass car objects to the map stage to be converted into model strings, pass these models to the filter stage, and send on any matching items to the distinct stage. 它将继续将car对象传递到map阶段以转换为模型字符串,将这些模型传递到filter阶段,并将任何匹配的项目发送到distinct阶段。 It cannot tell that this is futile, because it doesn't understand that nothing else can pass through the algorithm; 它不能说这是徒劳的,因为它不明白没有别的东西可以通过算法; it simple executes the instructions. 它很简单地执行指令。

But we do understand. 我们明白了。 At most two distinct models can pass through the distinct() stage. 最多两个不同的模型可以通过distinct()阶段。 So, we follow this with: 所以,我们遵循这个:

           .limit(2)

This is a short-circuiting stateful intermediate operation . 这是一种短路状态中间操作 It maintains a count of the number of items which pass through, and after the indicated amount, it terminates the stream, causing all subsequent items to be discarded without even starting down the pipeline. 它保持通过的项目数的计数,并且在指示的数量之后,它终止流,导致所有后续项目被丢弃,甚至没有开始管道。

At this point in the pipeline, there can only be at most two elements in the stream: "Focus" or "Civic" or neither or both. 在流水线的这一点上,流中最多只能有两个元素:“Focus”或“Civic”,或者两者都没有。 But if both, then the stream has been truncated and is at the end. 但如果两者都有,那么流已被截断并且结束了。

           .count() == 2;

Count up the number of items that made it through the pipeline, and test against the desired number. 计算通过管道的项目数量,并根据所需的数量进行测试。

If we found both models, the stream will immediate terminate, count() will return 2, and true will be returned. 如果我们找到两个模型,流将立即终止, count()将返回2,并返回true If both models are not present, of course, the stream is processed until the bitter end, count() will return a value less that two, and false will result. 当然,如果两个模型都不存在,则处理流直到苦味结束, count()将返回小于2的值,并且将导致false


Example, using an infinite stream of models. 例如,使用无限的模型流。 Every third model is a "Civic", every 7th model is a "Focus", the remainder are all "Model #": 每个第三个模型都是“思域”,每个第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);

Output: 输出:

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

Notice that 3 models got through the filter() , but only 2 made it past distinct() and limit() . 请注意,有3个模型通过filter() ,但只有2个模型通过distinct()limit() More importantly, notice that true was returned long before the end of the infinite stream of models was reached. 更重要的是,请注意,在无限流模型结束之前很久就返回了true


Generalizing the solution, since the OP wants something that could work with people, or credit cards, or IP addresses, etc., and the search criteria is probably not a fixed set of two items: 推广解决方案,因为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();

Here, given an arbitrary models set, existence of any particular set of car models may be obtained, not limited to just 2. 这里,给定任意models集,可以获得任何特定汽车模型组的存在,而不仅限于2。

You can do: 你可以做:

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;

or even with Set 甚至是Set

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

and with distinct distinct

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

The reason it "doesn't feel right" is because you are forcing the stream API to do something it doesn't want to do. 它“感觉不对”的原因是因为你强迫流API执行它不想做的事情。 You would almost surely be better off with a traditional loop: 使用传统循环几乎肯定会更好:

boolean hasFocus = false, hasCivic = false;
for (Car c : reportElements) {
    if ("Focus".equals(c.getModel())) hasFocus = true;
    if ("Civic".equals(c.getModel())) hasCivic = true;
    if (hasFocus & hasCivic) return true;
}
return false;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM