2017-07-30 80 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(); 
相關問題