2017-07-30 94 views
5

有两个类和两个相应的清单:的Java 8流:找到基于从另一个列表中值匹配计算条件的一​​个列表项

class Click { 
    long campaignId; 
    Date date; 
} 

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

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

,并希望找到所有Click S IN clicks是:

  1. 有相应的Campaigncampaigns列表,即Campaign具有相同campaignId

  2. Campaigntype = “前瞻性” 和

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

到目前为止,我有以下实现(这似乎是混乱和复杂的给我):

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

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

+8

我认为你真正需要做的就是将更大的lambda提取到一个命名方法中。 –

+0

@JoeC这真的有可能吗?这个lambda是指点击和广告系列,这意味着我将需要使用BiPredicate,而过滤器接受Predicate –

+5

'点击 - > checkCampaigns(点击,广告系列)' –

回答

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字段,只是写得很快。

1

有一点很突出,就是您的第二项要求与匹配无关,仅限于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); 
} 

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

  1. 不要使用旧的Date类,而是使用新的java.time APILocalDate
  2. 如果Campaigntype只能有一些预定义的值(如“提交”,“勘察”,“接受”...),那么enum比一般的String更适合。
1

那么,有一个非常简单的方法来解决你的问题IMO,来自Holger的原创想法(我会找到问题,并将其链接到这里)。

你可以定义你的方法,做了检查(我已经简化它只是一个位):

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

,并定义绑定参数的函数:

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

这确实很整洁,但OP表示他们发现他们目前的解决方案“令人困惑和复杂”,所以我无法想象这个问题不那么重要。 – user1803551

3

我的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(); 
相关问题