Reputation: 1582
I have a map of map on which I want to stream,
Map<LocalDate, Map<Integer, List<String>>> mainMap;
and remove the values which match another map,
Map<LocalDate, List<String>> childMap;
Ex. mainMap contains
{2016-02-23={120=[1,2,3], 121=[1,2,3], 122=[1,2,3], 123=[1,2,3]}}
and childMap contains
{2016-02-23=[120,123]}
I want to remove these values from mainMap.
Expected Output =>
{2016-02-23={121=[1,2,3], 122=[1,2,3]}}
How to do it in Java 8?
Upvotes: 3
Views: 923
Reputation: 298529
An alternative to Tunaki’s answer is
mainMap.forEach((k, v) -> childMap.getOrDefault(k, emptyList())
.stream().map(Integer::valueOf).forEach(v::remove));
the difference is that Tunaki’s solution will iterate over the contents of the sub-maps contained in mainMap
and perform a lookup on childMap
for each, whereas the solution above will iterate over the lists found in childMap
and perform a lookup the submaps of mainMap
. This is preferable when the sizes of both, the submaps and the lists, grow.
One thing that hasn’t been addressed, is whether mappings of the mainMap
should get removed if their submaps get emptied due to the operation. Supporting removal of outer mappings is not possible in one pass when iterating over the map that is going to be modified (unless you resort to an old loop using an Iterator
).
A solution is to iterate over the childMap
then:
childMap.forEach((k,v) -> mainMap.computeIfPresent(k, (d,m) -> {
v.stream().map(Integer::valueOf).forEach(m::remove);
return m.isEmpty()? null: m;
}));
Note that most complication arises from the type mismatch between your two maps, so a conversion between the childMap
’s String
s to the mainMap
’s Integer
s has to be performed. If they had the same type, e.g. if childMap
was a Map<LocalDate, List<Integer>>
or mainMap
was Map<LocalDate, Map<String, List<String>>>
, the solution not removing empty maps was as simple as
mainMap.forEach((k, v) -> v.keySet().removeAll(childMap.getOrDefault(k, emptyList())));
and the solution which will also remove empty mappings from the outer map:
childMap.forEach((k,v) -> mainMap.computeIfPresent(k, (d,m) -> {
m.keySet().removeAll(v);
return m.isEmpty()? null: m;
}));
The code above may remove mappings to an initially empty map. If you want to remove mappings only if the map has been emptied during this operation, i.e. not touch those which were empty to begin with, the code becomes even simpler:
childMap.forEach((k,v) ->
mainMap.computeIfPresent(k, (d,m) -> m.keySet().removeAll(v) && m.isEmpty()? null: m));
Upvotes: 2
Reputation: 137259
You could do it in-place with the following:
mainMap.forEach((k, v) ->
v.keySet().removeIf(s ->
Optional.ofNullable(childMap.get(k)).map(o -> o.contains(s.toString())).orElse(false)
)
);
This iterates through the mainMap
. Then, concerning the value of that map, which is a Map<Integer, List<String>>
, it removes all the integer keys (converted to a String
) where the childMap
for the current date points to a list containing that key. Note that the integer key is not removed if the child map does not contain a list for the current date.
A full sample code is the following, which prints your desired output:
public static void main(String[] args) {
Map<LocalDate, Map<Integer, List<String>>> mainMap = new HashMap<>();
Map<Integer, List<String>> map = new HashMap<>();
map.put(120, Arrays.asList("1", "2", "3"));
map.put(121, Arrays.asList("1", "2", "3"));
map.put(122, Arrays.asList("1", "2", "3"));
map.put(123, Arrays.asList("1", "2", "3"));
mainMap.put(LocalDate.of(2016, 2, 23), map);
Map<LocalDate, List<String>> childMap = new HashMap<>();
childMap.put(LocalDate.of(2016, 2, 23), Arrays.asList("120", "123"));
mainMap.forEach((k, v) ->
v.keySet().removeIf(s ->
Optional.ofNullable(childMap.get(k)).map(o -> o.contains(s.toString())).orElse(false)
)
);
System.out.println(mainMap);
}
Upvotes: 4