Java8Streams:从一个列表中查找符合根据另一个列表中的值计算的条件的项
有两个类和两个对应的列表:Java8Streams:从一个列表中查找符合根据另一个列表中的值计算的条件的项,java,java-8,java-stream,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 = ..; 我想知道是否有更简单的解决方案。公共列表查找匹配单击(列表CMP
class Click {
long campaignId;
Date date;
}
class Campaign {
long campaignId;
Date start;
Date end;
String type;
}
List<Click> clicks = ..;
List<Campaign> campaigns = ..;
我想知道是否有更简单的解决方案。公共列表查找匹配单击(列表CMP,列表单击){
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));
}
列出cmpsProspective=cmps.stream().filter(cmp->“prospective”.equals(cmp.type)).collect(collector.toList());
返回clicks.stream().filter(c->matchesAnyCmp(c,cmpsProspective).collect(collector.toList());
}
公共布尔匹配SANYCMP(单击,列出CMP){
返回cmps.stream().anyMatch(单击->cmp.start.before(单击.date)和&cmp.end.after(单击.date));
}
替换getter字段,只需快速编写即可。最突出的一点是,您的第二个要求与匹配无关,它只是
活动的一个条件。您必须测试这是否对您更有利:
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());
否则,我从未见过一个streams解决方案不涉及在第1个集合的谓词中对第2个集合进行流式处理,因此您所做的不会比您所做的更好。就可读性而言,如果您对它感到困惑,请创建一个测试布尔条件的方法并调用它:
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);
}
最后,提出两项不相关的建议:
不要使用旧的Date
类,而是使用新的
如果活动
的类型
只能有一些预定义的值(如“已提交”、“寻找客户”、“已接受”…),则枚举
比一般的字符串
更合适
嗯,在我看来,有一个非常巧妙的方法来解决你的问题,霍尔格的原始想法(我会找到问题并将其链接到这里)
您可以定义执行检查的方法(我只是简化了一点):
static boolean checkClick(列出活动,单击){
返回活动.stream().anyMatch(camp->camp.getCampaignId())
==单击.getCampaignId());
}
并定义一个绑定参数的函数:
public static <T, U> Predicate<U> bind(BiFunction<T, U, Boolean> f, T t) {
return u -> f.apply(t, u);
}
公共静态谓词绑定(双函数f,T){
返回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());
BiFunction-BiFunction=YourClass::checkClick;
谓词=绑定(双功能,活动);
clicks.stream()
.filter(谓词::测试)
.collect(Collectors.toList());
我的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());
Map Map=campeties.stream().filter(c->c.type.equals(“prospecting”))
.collect(Collectors.groupingBy(c->c.campaiid));
单击.stream().filter(k->map.containsKey(k.campaid))
.filter(k->map.get(k.activitid).stream().anyMatch(c->c.start.before(k.date))和c.end.after(k.date)))
.collect(Collectors.toList());
该代码并不比原始代码短很多。但它会将性能从O(nm)提高到O(n+m),正如@Marco13在注释中提到的。如果您想缩短,请尝试
Map Map=streamx.of(活动)
.filter(c->c.type.equals(“prospecting”)).groupingBy(c->c.campaignId);
StreamEx.of(clicks).filter(k->map.containsKey(k.campaid))
.filter(k->map.get(k.activitid).stream().anyMatch(c->c.start.after(k.date)&&c.end.before(k.date)))
.toList();
我认为您真正需要做的就是将较大的lambda提取到一个命名方法中。@JoeC这真的有可能吗?这个lambda同时指单击和活动,这意味着我需要使用BiPredicate,而过滤器接受谓词单击->检查活动(单击,活动)
不管(感知或真实)基于流的解决方案的优雅:我强烈建议使用一种算法不同的方法。anyMatch
看起来很方便,但是有O(n),所以对于m
点击和n
活动,这是O(nm)。构建一个Map
用作查找会将其更改为O(n+m)(可能有一些TreeMap
支持按日期索引,但这可能是第二步)顺便说一句,麻烦的Date
类已经被该类取代。尽可能避免Date
,但在必须转换的地方,您可以使用添加到旧类中的新方法进行转换:这确实很好,但是OP说他们发现当前的解决方案“混乱而复杂”,所以我无法想象这个解决方案会不那么复杂。
BiFunction<List<Campaign>, Click, Boolean> biFunction = YourClass::checkClick;
Predicate<Click> predicate = bind(biFunction, campaigns);
clicks.stream()
.filter(predicate::test)
.collect(Collectors.toList());
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());
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();