haint504
haint504

Reputation: 103

Group objects by multiple attributes with Java 8 stream API

Given we have a list of Bank, each Bank have multiple offices,

public class Bank {
   private String name;
   private List<String> branches;
   public String getName(){
       return name;
   }
   public List<String> getBranches(){
       return branches;
   }
}

For example:

Bank "Mizuho": branches=["London", "New York"]
Bank "Goldman": branches = ["London", "Toronto"]

Given a list of banks, I would have a map of bank representation for each city. In the example above, I need a result of

Map["London"] == ["Mizuho", "Goldman"]
Map["New York"] == ["Mizuho"]
Map["Toronto"] == ["Goldman"]

How can I achieve that result using Java 8 API? Using pre-Java8 is easy, but verbose. Thank you.

Upvotes: 4

Views: 1803

Answers (4)

user_3380739
user_3380739

Reputation: 1244

I think solution provided by @JB Nizet is one of the most simple/efficient solutions. it can also be rewritten by forEach

banks.forEach(b -> b.getBranches().forEach(ch -> result.computeIfAbsent(ch, k -> new ArrayList<>()).add(b)));

Another short solution by Stream with abacus-common

Map<String, List<Bank>> res = Stream.of(banks)
          .flatMap(e -> Stream.of(e.getBranches()).map(b -> Pair.of(b, e)))
          .collect(Collectors.toMap2());
    

Upvotes: 0

fps
fps

Reputation: 34450

You could do it using the version of Stream.collect that accepts a supplier, an accumulator function and a combiner function, as follows:

Map<String, List<Bank>> result = banks.stream()
    .collect(
        HashMap::new,
        (map, bank) -> bank.getBranches().forEach(branch ->
            map.computeIfAbsent(branch, k -> new ArrayList<>()).add(bank)),
        (map1, map2) -> map2.forEach((k, v) -> map1.merge(k, v, (l1, l2) -> {
            l1.addAll(l2);
            return l1;
        })));

Upvotes: 1

Eugene
Eugene

Reputation: 120848

 banks.flatMap(bank -> bank.getBranches()
             .stream()
             .map(branch -> new AbstractMap.SimpleEntry<>(branch, bank)))
       .collect(Collectors.groupingBy(
             Entry::getKey, 
             Collectors.mapping(Entry::getValue, Collectors.toList())));

Result would be:

{London=[Mizuho, Goldman], NewYork=[Mizuho], Toronto=[Goldman]}

Upvotes: 4

JB Nizet
JB Nizet

Reputation: 691635

Map<String, Set<Bank>> result = new HashMap<>();
for (Bank bank : banks) {
    for (String branch : bank.getBranches()) {
        result.computeIfAbsent(branch, b -> new HashSet<Bank>()).add(bank);
    }
}

Upvotes: 6

Related Questions