Java 8 流:从一个列表中查找与基于另一个列表中的值计算的条件相匹配的项目

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45397570/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 02:00:56  来源:igfitidea点击:

Java 8 streams: find items from one list that match conditions calculated based on values from another list

javajava-8java-stream

提问by Andrey Yaskulsky

Have two classes and two corresponding lists:

有两个类和两个对应的列表:

class Click {
   long campaignId;
   Date date;
}

class Campaign {
   long campaignId;
   Date start;
   Date end;
   String type;
}

List<Click> clicks = ..;
List<Campaign> campaigns = ..;

And want to find all Clicks in clicksthat:

并想在其中找到所有Clicks clicks

  1. Have a corresponding Campaignin campaignslist, i.e., Campaignwith the same campaignIdAND

  2. This Campaignhas type= "prospective" AND

  3. This Campaigns.start< click.date< Campaigns.end

  1. Campaigncampaigns列表中有一个对应的,即Campaign具有相同的campaignIdAND

  2. Campaigntype= "prospective" AND

  3. Campaigns.start< click.date<Campaigns.end

So far I have the following implementation (which seems confusing and complex to me):

到目前为止,我有以下实现(这对我来说似乎令人困惑和复杂):

clicks.
        stream().
        filter(click -> campaigns.stream().anyMatch(
                campaign -> campaign.getCampaignType().equals("prospecting") &&
                        campaign.getCampaignId().equals(click.getCampaignId()) &&
                        campaign.getStart().after(click.getDate()) &&
                        campaign.getEnd().before(click.getDate()))).
        collect(toList());

I wonder if there is simpler solution for the problem.

我想知道这个问题是否有更简单的解决方案。

采纳答案by user1803551

One thing that stands out is that your 2nd requirement has nothing to do with the matching, it's a condition on campaignsonly. You'd have to test if this is any better for you:

突出的一件事是您的第二个要求与匹配无关,它只是一个条件campaigns。您必须测试这是否对您更好:

clicks.stream()
    .filter(click -> campaigns.stream()
        .filter(camp -> "prospecting".equals(camp.type))
        .anyMatch(camp -> 
            camp.campaignId == click.campaignId &&
            camp.end.after(click.date) &&
            camp.start.before(click.date)
        )
    )
    .collect(Collectors.toList());

Otherwise, I have never seen a streams solution which does not involve streaming the 2nd collection inside the predicate of the 1st, so you can't do much better than what you did. In terms of readability, if it looks that confusing to you then create a method that test for the boolean condition and call it:

否则,我从未见过不涉及在第一个谓词中流式传输第二个集合的流解决方案,因此您不能做得比您所做的更好。在可读性方面,如果它看起来让您感到困惑,那么创建一个测试布尔条件的方法并调用它:

clicks.stream()
    .filter(click -> campaigns.stream()
        .filter(camp -> "pre".equals(camp.type))
        .anyMatch(camp -> accept(camp, click))
    )
    .collect(Collectors.toList());

static boolean accept(Campaign camp, Click click) {
    return camp.campaignId == click.campaignId &&
            camp.end.after(click.date) &&
            camp.start.before(click.date);
}

Finally, 2 unrelated suggestions:

最后,2个不相关的建议:

  1. Don't use the old Dateclass, instead use the new java.time API's LocalDate.
  2. If Campaign's typecan only have some predefined values (like "submitted", "prospecting", "accepted"...) then an enumwould be a better fit than a general String.
  1. 不要使用旧Date类,而是使用新的java.time APILocalDate
  2. 如果Campaign'stype只能有一些预定义的值(如“提交”、“展望”、“接受”...),那么 anenum将比一般 更适合String

回答by albert_nil

public List<Click> findMatchingClicks(List<Campaign> cmps, List<Click> clicks) {
    List<Campaign> cmpsProspective = cmps.stream().filter(cmp -> "prospective".equals(cmp.type)).collect(Collectors.toList());
    return clicks.stream().filter(c -> matchesAnyCmp(c, cmpsProspective).collect(Collectors.toList());
}

public boolean matchesAnyCmp(Click click, List<Campaign> cmps) {
     return cmps.stream().anyMatch(click -> cmp.start.before(click.date) && cmp.end.after(click.date));
}

Replace fields for getters, just wrote it quick.

替换 getter 的字段,只需快速编写即可。

回答by Eugene

Well, there is a very neat way to solve your problem IMO, original idea coming from Holger (I'll find the question and link it here).

嗯,有一种非常巧妙的方法可以解决您的问题 IMO,来自 Holger 的原始想法(我会找到问题并在此处链接)。

You could define your method that does the checks (I've simplified it just a bit):

您可以定义执行检查的方法(我已经简化了一点):

static boolean checkClick(List<Campaign> campaigns, Click click) {
    return campaigns.stream().anyMatch(camp -> camp.getCampaignId() 
               == click.getCampaignId());
}

And define a function that binds the parameters:

并定义一个绑定参数的函数:

public static <T, U> Predicate<U> bind(BiFunction<T, U, Boolean> f, T t) {
    return u -> f.apply(t, u);
}

And the usage would be:

用法是:

BiFunction<List<Campaign>, Click, Boolean> biFunction = YourClass::checkClick;
Predicate<Click> predicate = bind(biFunction, campaigns);

clicks.stream()
      .filter(predicate::test)
      .collect(Collectors.toList());

回答by 123-xyz

My 2 cents: Since there is no much boilerplate code in OP. So it may be not possible/necessary to reduce the lines/characters in the codes. we could rewrite it to make it a little more clearly:

我的 2 美分:因为 OP 中没有太多样板代码。因此,减少代码中的行/字符可能是不可能/不必要的。我们可以重写它以使其更清楚一点:

Map<Long, List<Campaign>> map = campaigns.stream().filter(c -> c.type.equals("prospecting"))
                                         .collect(Collectors.groupingBy(c -> c.campaignId));

clicks.stream().filter(k -> map.containsKey(k.campaignId))
               .filter(k -> map.get(k.campaignId).stream().anyMatch(c -> c.start.before(k.date) && c.end.after(k.date)))
               .collect(Collectors.toList());

The code is not much shorter than original code. but it will improve performance from O(nm) to O(n+m), as @Marco13 mentioned in the comments. if you want shorter, try StreamEx

代码并不比原始代码短多少。但它会将性能从 O(nm) 提高到 O(n+m),正如评论中提到的@Marco13。如果你想要更短,试试StreamEx

Map<Long, List<Campaign>> map = StreamEx.of(campaigns)
                .filter(c -> c.type.equals("prospecting")).groupingBy(c -> c.campaignId);

StreamEx.of(clicks).filter(k -> map.containsKey(k.campaignId))
        .filter(k -> map.get(k.campaignId).stream().anyMatch(c -> c.start.after(k.date) && c.end.before(k.date)))
       .toList();