2017-05-31 278 views
17

我想从地图映射转换内部地图。使用Java Stream API转换类型X到Y <K, Map<V, X>>使用Java Stream API

旧地图:Map<String, Map<LocalDate, Integer>>整数表示秒

新地图:Map<String, Map<LocalDate, Duration>>

我曾尝试创建新的内部地图,却得到了一个错误

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

相关的问题:https://stackoverflow.com/questions/25864559/iterate-though-a-map-of-maps-with-lambda -in-java-8 –

+1

相关(来自同一个OP):[使用Collectors.summingInt时如何获得自定义类型而不是Integer?](https: //stackoverflow.com/qu estions/44285623/how-to-get-a-custom-type-in​​stead-of-integer-when-using-collectors-summingint) –

回答

2

我宁愿迭代在旧地图的条目上并通过内部地图流式传输:

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

否则,你需要你的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()))))); 
+0

第一个答案(https:// stackoverflow。 com/a/44283570/4854931) – Alex78191

+0

@ Alex78191是的,他的速度有点快 –

2

如果我理解你正确,你可以看到下面的代码:

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

      ); 
10

如果你想紧凑的代码,你可以使用

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)))); 

如果您想避免不必要的散列操作,您可以将其扩大一点

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))); 
}); 
+1

你这样拴住的方式......花了一段时间才搞定它。不错 – Eugene

7

快速,干净

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); 
}); 
+0

@Holger你比较快,根据年龄大小排序。 – Alex78191

+2

@Holger https://i.imgur.com/KYH1cDP.png – Alex78191

+1

9秒,生命是一场竞赛:) –

5

还有一...

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()))))); 
+0

它与https://stackoverflow.com/a/44283598/4854931中的第二个变体相同 – Alex78191

+0

@ Alex78191的确,真的很接近! – Eugene

+0

从[第一回答](https://stackoverflow.com/a/44283570/4854931) – Alex78191

3

让我们提取的辅助方法toMap以使问题更加简单。

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可以作为进一步简化的代码由于主逻辑被复制:适应在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,你可以在任何地方使用它,需要一个Function<Map<?,?>,Map<?,?>>的价值适应另一个值,例如:

Map<String, Map<LocalDate, Duration>> result = Optional.of(oldMap) 
     .map(mapping(mapping(seconds -> Duration.ofSeconds(seconds.longValue())))) 
     .get(); 
5

我的两分钱,创建一个方法来变换Map<K, V1>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)); 
} 

然后你的代码变成:

Map<String, Map<LocalDate, Duration>> transformed 
    = transformValues(maps, map -> transformValues(map, Duration::ofSeconds)); 
+0

不错的主意。和我一样... –

3

为了完整起见,下面是一个使用一个版本的番石榴的Maps.transformValues

Map<String, Map<LocalDate, Duration>> result = 
    Maps.transformValues(oldMap, m -> Maps.transformValues(m, Duration::ofSeconds)); 
+0

我忘了它。感谢提醒。我已经在重构我的答案,看起来像你的答案,但有人已经在同一时间回答了我的想法。所以我增加了'map(Function )'的另一个用法。 –

+0

@ holi-java嗨!我赞成你的答案。我喜欢它应用一个函数来懒惰地改变值。先生, –

+0

谢谢。但我想说,在这种情况下,作为你的最终方式更好。 –

1

下面是StreamEx解决方案:

EntryStream.of(oldMap) 
      .mapValues(v -> EntryStream.of(v).mapValues(Duration::ofSeconds).toMap()) 
      .toMap();