Reputation: 18531
I have a map of maps
siteId -> (AppName -> App)
I want to iterate all of the Apps in the inner map and create a new map of
(appId -> App)
I do it without stream
Map<String, App> result = new HashMap<>();
siteIdToAppNameToAppMap.forEach((siteId, map) ->
map.forEach((appName, app) ->
result.put(app.token, app)
)
);
How do I do it with stream?
Upvotes: 3
Views: 2208
Reputation: 56453
A slightly different variant to @Anton Balaniuc's answer.
Map<String, App> resultSet =
siteIdToAppNameToAppMap.values()
.stream()
.map(Map::values)
.flatMap(Collection::stream)
.collect(Collectors.toMap(App::getToken,
Function.identity(), (left, right) -> {
throw new RuntimeException("duplicate key");
},
HashMap::new));
This solution creates a stream from the siteIdToAppNameToAppMap
map values, which we then perform a map
operation to the map values yielding a Stream<Collection<App>>
and then flatMap
will collapse all the nested Stream<Collection<App>>
to a Stream<App>
and then finally the toMap
collector will return a Collector
that accumulates the elements into a Map whose keys are the return value of App::getToken
and values are the return value of Function.identity()
.
The function (left, right) -> { throw new RuntimeException("duplicate key");}
above is the merge function, used to resolve collisions between values associated with the same key. in this particular case, you don't need it but it's only there so we can use this overload of the toMap
collector which then allows us to specify that we specifically want a HashMap
instance.
All, the other toMap
overloads don't guarantee on the type, mutability, serializability, or thread-safety of the Map returned.
Note - if you're not expecting duplicate keys then throwing an exception, as shown above, is the way to go as it indicates there's a problem as opposed to doing something else. However, if in the case of a duplicate key you want to return a value then you can simply change the merge function to (left, right) -> left
or (left, right) -> right
depending on what you want.
Upvotes: 3
Reputation: 11739
What about something like this?
siteIdToAppNameToAppMap.values()
.stream()
.flatMap(m -> m.values().stream())
.collect(
Collectors.toMap(App::getToken, Function.identity())
);
We will need to use Stream#flatMap to extract App
from nested map. So stream().values()
will give us Stream<Map<AppName,App>>
now we need to transform it into Stream<App>
using flatMap:
Stream<Map<AppName,App>> -> flatMap -> Stream<App>
and after is we can finally collect to a new Map<AppId,App>
Upvotes: 4