如何验证 Java 8 流中是否有两个特定元素?

2022-09-02 20:53:09

假设我有,我想搜索该列表,以验证我是否同时具有思域和焦点。如果它是OR,那很容易,因为我可以在.请记住,我不能为这种类型的AND做。List<Car>.filter()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;

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

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

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


答案 1

您可以对 上的流进行筛选,然后在返回 时运行收集器。然后,您可以测试您的集合是否同时包含这两个键。"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");

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


以下是一种“快速成功”的短路方法。(更新以包括评论和评论中的澄清,如下所示)

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()

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

这会过滤汽车模型流,仅允许“Focus”和“Civic”汽车模型通过。

           .distinct()

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

在管道中的这一点上,流中最多只能有两个元素:“Focus”或“Civic”,或者两者都不是,或者两者兼而有之。我们知道这一点,因为我们知道只会通过这两个模型,我们知道这将删除任何重复项。filter(...)distinct()

但是,此流管道本身并不知道这一点。它将继续将汽车对象传递到舞台上,以转换为模型字符串,将这些模型传递到舞台,并将任何匹配的物品发送到舞台。它无法分辨这是徒劳的,因为它不明白没有其他任何东西可以通过算法;它简单地执行指令。mapfilterdistinct

我们确实理解。最多两个不同的模型可以通过舞台。因此,我们遵循以下条件:distinct()

           .limit(2)

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

在管道中的这一点上,流中最多只能有两个元素:“Focus”或“Civic”,或者两者都不是,或者两者兼而有之。但是,如果两者兼而有之,则流已被截断并处于末尾。

           .count() == 2;

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

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


示例,使用无限的模型流。每三个模型中就有一个是“思域”,每七个模型是一个“焦点”,其余的都是“模型#”:

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 个模型通过了 ,但只有 2 个模型通过了 。更重要的是,请注意,早在无限模型流的结束之前很久就返回了。filter()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();

这里,给定一个任意的集合,可以获得任何特定模型集合的存在,而不限于2。models


答案 2

您可以执行以下操作:

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;

推荐