英文:
Traverse two collections looking for matches, falling back to certain value for those elements that didn't match
问题
在初始时,我使用嵌套的for
循环解决了这个问题,但我尝试使用流API来解决它(我要说是不成功的)。基本上,我需要遍历两个集合,根据给定条件寻找匹配项:A.id === B.identifier
,以创建一个最终的A
元素映射,键是A
元素,值是匹配项的对应A.foo + B.bar
。
对我来说,使用流API解决方案的问题在于要求的第二部分:如果来自A
的某些元素不满足条件,应分配默认值(基于函数调用)...而我不知道如何在使用流API时与其余条件一起实现这一点。
请注意,最终的集合将具有与(初始)
A
相同数量的元素,但会进行修改。
是否有一种有效的方法来使用流API来实现这个,而不是使用嵌套的for
循环和一些if
条件?
英文:
Say I have two collections of A { id, foo }
and B { identifier, bar }
.
Initially I solved this problem using nested for
-loops, but I did try to solve it (unsuccessful I would say) using the Stream API. Basically I need to traverse both collections looking for matches based on a given condition: A.id === B.identifier
, in order to create a final map of A
elements as keys and the values would be the corresponding A.foo + B.bar
for the matches.
The problem for me in the solution using the Stream API was the second part of the requirements: if some element(s) from A
did not satisfy the condition, a default value should be assigned (based on a function call)... and I don't know how to plug this while using the Stream API along with the rest of the conditions.
> Notice that the final collection will have as many elements as (initial) A
— with the modifications.
Is there an efficient way to accomplish this using the Stream API rather than using nested for
-loops and a couple of if
conditions?
答案1
得分: 2
可以使用临时集合吗?如果可以,我建议将 List<B>
转换为 Map<Identifier, Bar> mapOfB
,这样你可以更轻松、更快速地找到指定的 B.identifier
,而无需为每个 A 元素遍历 B 列表。
然后,你可以简单地这样做:
listOfA.stream()
.collect(toMap(a -> a, a -> {
var bar = mapOfB.get(a.getId());
if (bar == null) {
bar = getDefault();
}
return a.getFoo() + bar;
}));
如果无法创建临时 Map:
listOfA.stream()
.collect(toMap(a -> a, a -> {
var bar = getBarFromBOrDefault(listOfB, a.getId());
return a.getFoo() + bar;
}))
private Bar getBarFromBOrDefault(List<B> listOfB, Id id) {
return listOfB.stream()
.filter(b -> b.getIdentity() == id) // or use .equals() here if it's not a primitive type
.findFirst()
.map(B::getBar)
.orElseGet(() -> getDefault());
}
英文:
Can you use the temporary collection? If yes, I would suggest to convert List<B>
into Map<Identifier, Bar> mapOfB
, so you can much easier and faster find the specified B.identifier
without need to traverse the B list for each A element.
Then you can simply do something like:
listOfA.stream()
.collect(toMap(a -> a, a -> {
var bar = mapOfB.get(a.getId());
if (bar == null) {
bar = getDefault();
}
return a.getFoo() + bar;
}));
If you can't create the temporary Map:
listOfA.stream()
.collect(toMap(a -> a, a -> {
var bar = getBarFromBOrDefault(listOfB, a.getId());
return a.getFoo() + bar;
}))
private Bar getBarFromBOrDefault(List<B> listOfB, Id id) {
return listOfB.stream()
.filter(b -> b.getIdentity() == id) // or use .equals() here if it's not a primitive type
.findFirst()
.map(B::getBar)
.orElseGet(() -> getDefault());
}
答案2
得分: 2
以下是代码的翻译部分:
你可以将每个A
映射到匹配的B
列表:
List<A> as = ...
List<B> bs = ...
as.stream()
.map(a -> new Pair<>(a, bs.stream()
.filter(b -> matches(a, b))
.toList()))
.collect(Collectors.toMap(p -> p.fst, p -> p.snd));
为了使这个工作,你需要声明自己的Pair
类型,这在JDK中明显缺失:
record Pair<F, S>(F fst, S snd) {}
请注意,即使没有嵌套的for循环,仍然存在嵌套的迭代:每个A
都映射到一个B
列表,这是通过遍历所有B
来查找匹配的B
来获得的。
更新
为了考虑在没有匹配项时返回默认值的情况,您可以将连接移到一个辅助方法中:
private List<B> join(A a, List<B> bs, B defaultB) {
List<B> matchingBs = bs.stream()
.filter(b -> matches(a, b))
.toList();
return matchingBs.isEmpty()
? List.of(defaultB)
: matchingBs;
}
并在流处理中提供一个默认值:
as.stream()
.map(a -> new Pair<>(a, join(a, bs, defaultB(a)))
.collect(Collectors.toMap(p -> p.fst, p -> p.snd));
英文:
You can map each A
to the list of matching B
s:
List<A> as = ...
List<B> bs = ...
as.stream()
.map(a -> new Pair<>(a, bs.stream()
.filter(b -> matches(a, b))
.toList()))
.collect(Collectors.toMap(p -> p.fst, p -> p.snd));
For this to work you need to declare you own Pair
type, which is prominently missing from the JDK.
record Pair<F, S>(F fst, S snd) {}
Note that even there is no nested for loops, there is still nested iteration: each A
is mapped to a list of B
s, which is obtained by traversing all B
s to find the ones that match.
Update
To account for the case where you want to return a default value should there be no matches, you can move the joining to a helper method:
private List<B> join(A a, List<B> bs, B defaultB) {
List<B> matchingBs = bs.stream()
.filter(b -> matches(a, b))
.toList();
return matchingBs.isEmpty()
? List.of(defaultB)
: matchingBs;
}
and supply a default value from within the stream processing
as.stream()
.map(a -> new Pair<>(a, join(a, bs, defaultB(a))))
.collect(Collectors.toMap(p -> p.fst, p -> p.snd));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论