遍历两个集合寻找匹配项,对于未匹配的元素,返回特定值。

huangapple go评论67阅读模式
英文:

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 Bs:

List&lt;A&gt; as = ...
List&lt;B&gt; bs = ...

as.stream()
    .map(a -&gt; new Pair&lt;&gt;(a, bs.stream()
                .filter(b -&gt; matches(a, b))
                .toList()))
    .collect(Collectors.toMap(p -&gt; p.fst, p -&gt; p.snd));

For this to work you need to declare you own Pair type, which is prominently missing from the JDK.

record Pair&lt;F, S&gt;(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 Bs, which is obtained by traversing all Bs 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&lt;B&gt; join(A a, List&lt;B&gt; bs, B defaultB) {
    List&lt;B&gt; matchingBs = bs.stream()
        .filter(b -&gt; 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 -&gt; new Pair&lt;&gt;(a, join(a, bs, defaultB(a))))
    .collect(Collectors.toMap(p -&gt; p.fst, p -&gt; p.snd));

huangapple
  • 本文由 发表于 2023年2月23日 22:21:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/75546093.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定