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

时间:2017-07-30 07:46:25

标签: java java-8 java-stream

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

class Click {
   long campaignId;
   Date date;
}

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

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

想要找到Click中的所有clicks

  1. Campaign列表中有相应的campaigns,即Campaign具有相同的campaignId AND

  2. Campaigntype =&#34;预期&#34;以及

  3. Campaigns.start&lt; click.date&lt; Campaigns.end

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

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

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

4 个答案:

答案 0 :(得分:4)

我的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());

代码并不比原始代码短。但它会提高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();

答案 1 :(得分:3)

嗯,有一种非常巧妙的方法来解决你的问题IMO,来自Holger的原创想法(我会在这里找到问题并链接它)。

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

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

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

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

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

用法是:

lein deps

答案 2 :(得分:2)

有一点值得注意的是,你的第二个要求与匹配无关,它只是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());

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

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

最后,2个无关的建议:

  1. 请勿使用旧的Date课程,而是使用新的java.time API LocalDate
  2. 如果Campaign&#39; type只能有一些预定义的值(例如&#34;提交&#34;,&#34;探索&#34;,&#34;接受& #34; ...)然后enum比普通String更合适。

答案 3 :(得分:1)

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

替换getter的字段,只需快速写下来。