17

I want to convert inner map from map of maps.

Old map: Map<String, Map<LocalDate, Integer>> Integer means seconds

New map: Map<String, Map<LocalDate, Duration>>

I have tried created new inner map, but got an error

Error: java: no suitable method found for putAll(java.util.stream.Stream<java.lang.Object>) method java.util.Map.putAll(java.util.Map<? extends java.time.LocalDate,? extends java.time.Duration>) is not applicable

oldMap.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,
                e -> new HashMap<LocalDate, Duration>() {{
                    putAll(
                        e.getValue().entrySet().stream()
                            .map(x -> new HashMap.SimpleEntry<LocalDate, Duration>
                                (x.getKey(), Duration.ofSeconds(x.getValue())))
                    );
                }}
            ));
Alex78191
  • 2,383
  • 2
  • 17
  • 24
  • related question: https://stackoverflow.com/questions/25864559/iterate-though-a-map-of-maps-with-lambda-in-java-8 – Andrei Epure May 31 '17 at 11:18
  • 1
    Related (from same OP): [How to get a custom type instead of Integer when using Collectors.summingInt?](https://stackoverflow.com/questions/44285623/how-to-get-a-custom-type-instead-of-integer-when-using-collectors-summingint) – Didier L May 31 '17 at 19:18

9 Answers9

11

If you want compact code, you may use

Map<String, Map<LocalDate, Duration>> newMap = new HashMap<>();
oldMap.forEach((s,o) -> o.forEach((d, i) ->
    newMap.computeIfAbsent(s, x->new HashMap<>()).put(d, Duration.ofSeconds(i))));

If you want to avoid unnecessary hash operations, you may expand it a bit

Map<String, Map<LocalDate, Duration>> newMap = new HashMap<>();
oldMap.forEach((s,o) -> {
    Map<LocalDate, Duration> n = new HashMap<>();
    newMap.put(s, n);
    o.forEach((d, i) -> n.put(d, Duration.ofSeconds(i)));
});
Holger
  • 285,553
  • 42
  • 434
  • 765
8

Quick and clean

HashMap<String, HashMap<LocalDate, Duration>> newMap = new HashMap<>();
oldHashMap.forEach((key, innerMap) -> {
    HashMap<LocalDate, Duration> newStuff = new HashMap<>();
    innerMap.forEach((k2,v2) -> newStuff.put(k2,Duration.ofSeconds(v2)));
    newMap.put(key, newStuff);
});
Andrei Epure
  • 1,746
  • 1
  • 21
  • 30
5

And one more...

 Map<String, Map<LocalDate, Duration>> newMap = map.entrySet().stream().collect(
            Collectors.toMap(Entry::getKey,
                    entry -> entry.getValue().entrySet().stream().collect(
                            Collectors.toMap(Entry::getKey, e -> Duration.ofSeconds(e.getValue())))));
Eugene
  • 117,005
  • 15
  • 201
  • 306
5

My two cents, create a method to transform Map<K, V1> to a Map<K, V2>:

public static <K,V1,V2> Map<K, V2> transformValues(final Map<K, V1> input, final Function<V1,V2> transform) {
    Function<Map.Entry<K, V1>, V2> mapper = transform.compose(Map.Entry::getValue);
    return input.entrySet().stream()
            .collect(toMap(Map.Entry::getKey, mapper));
}

Then your code becomes:

Map<String, Map<LocalDate, Duration>> transformed 
    = transformValues(maps, map -> transformValues(map, Duration::ofSeconds));
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
4

Let's extract a helper method toMap to makes the problem more simpler.

Map<String, Map<LocalDate, Duration>> result = oldMap.entrySet().stream()
    .map(entry -> new AbstractMap.SimpleEntry<>(
         entry.getKey(),
         entry.getValue().entrySet().stream()
                         .collect(toMap(it -> Duration.ofSeconds(it.longValue())))
//                                ^--- mapping Integer to Duration
    )).collect(toMap(Function.identity()));



<K, V, R> Collector<Map.Entry<K, V>, ?, Map<K, R>> toMap(Function<V, R> mapping) {
    return Collectors.toMap(
         Map.Entry::getKey,
         mapping.compose(Map.Entry::getValue)
    );
}

AND you can simplified the code as further since the primary logic is duplicated: adapts a value to another value in a Map.

Function<
    Map<String, Map<LocalDate, Integer>>,
    Map<String, Map<LocalDate, Duration>>
> mapping = mapping(mapping(seconds -> Duration.ofSeconds(seconds.longValue())));
//          |       |
//          |   adapts Map<LocalDate, Integer> to Map<LocalDate,Duration>
//          | 
//adapts Map<String,Map<LocalDate,Integer>> to Map<String,Map<LocalDate,Duration>>

Map<String, Map<LocalDate, Duration>> result = mapping.apply(oldMap);


<K, V1, V2> Function<Map<K, V1>, Map<K, V2>> mapping(Function<V1, V2> mapping) {
    return it -> it.entrySet().stream().collect(toMap(mapping));
}

THEN you can use it anywhere that needs a Function<Map<?,?>,Map<?,?>> to adapts the value to another value,for example:

Map<String, Map<LocalDate, Duration>> result = Optional.of(oldMap)
        .map(mapping(mapping(seconds -> Duration.ofSeconds(seconds.longValue()))))
        .get();
holi-java
  • 29,655
  • 7
  • 72
  • 83
3

I would prefer to iterate over the entries of your old map and streaming over the inner map:

for (Entry<String, Map<LocalDate, Integer>> entry : oldMap.entrySet()) {
    Map<LocalDate, Duration> asDuration = entry.getValue().entrySet().stream()
        .collect(Collectors.toMap(e -> e.getKey(), e -> Duration.ofSeconds(e.getValue().longValue())));
    newMap.put(entry.getKey(), asDuration);
}

Otherwise you need a second stream inside your collect:

newMap = oldMap.entrySet().stream()
        .collect(Collectors.toMap(s -> s.getKey(), s -> s.getValue().entrySet().stream()
        .collect(Collectors.toMap(e -> e.getKey(), e -> Duration.ofSeconds(e.getValue().longValue())))));
m7913d
  • 10,244
  • 7
  • 28
  • 56
Stefan Warminski
  • 1,845
  • 1
  • 9
  • 18
3

If i understand you correct,you can see following code:

oldMap.entrySet().stream()
            .collect(Collectors.toMap(x -> x.getKey(),
                    x -> {
                        Map<LocalDate, Duration> temp = new HashMap<>();
                        x.getValue().forEach((k, v) -> {
                            temp.put(k, Duration.ofSeconds(v));
                        });
                        return temp;
                    })

            );
dabaicai
  • 999
  • 8
  • 9
3

For completeness, here's a version that uses Guava's Maps.transformValues:

Map<String, Map<LocalDate, Duration>> result = 
    Maps.transformValues(oldMap, m -> Maps.transformValues(m, Duration::ofSeconds));
fps
  • 33,623
  • 8
  • 55
  • 110
  • I have forgot it. thanks for reminding. I already refactoring my answer that seems to looks like as yours, but someone already answered my idea at the same time. so I adds another usage of `map(Function)`. – holi-java May 31 '17 at 20:08
  • @holi-java hi! I upvoted your answer. I like it to apply a function to lazily transform the values. – fps May 31 '17 at 20:11
  • thanks, sir. but I want to say that the final way as yours is better in this case. – holi-java May 31 '17 at 20:17
  • 1
    @holi-java oh, thank you very much! Guava is so practical and elegant. Though it's not exactly the same as in your answer, because guava returns a map view. The functions are applied lazily, i.e. when accessing the methods of the map view. – fps May 31 '17 at 20:24
  • 1
    yes, sir. the actual mapping occurs in each `get(?)` at runtime, so it doesn't copy a `Map` to another `Map` just is referred a `Map` in wrapper class. – holi-java May 31 '17 at 20:31
1

Here is solution by StreamEx:

EntryStream.of(oldMap)
           .mapValues(v -> EntryStream.of(v).mapValues(Duration::ofSeconds).toMap())
           .toMap();
user_3380739
  • 1
  • 14
  • 14