Search code examples
javajava-stream

Traverse two collections looking for matches, falling back to certain value for those elements that didn't match


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?


Solution

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