Преобразование типа X в Y в MapK, MapV, X с использованием Java Stream API

17

Я хочу преобразовать внутреннюю карту из карты карт.

Старая карта: Map<String, Map<LocalDate, Integer>> Целое число означает секунды

Новая карта: Map<String, Map<LocalDate, Duration>>

Я попытался создать новую внутреннюю карту, но получил ошибку

  

Ошибка: java: не найдено подходящего метода для putAll(java.util.stream.Stream<java.lang.Object>)       метод java.util.Map.putAll(java.util.Map<? extends java.time.LocalDate,? extends java.time.Duration>) не применим

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 31.05.2017 в 13:12
источник
  • родственный вопрос: stackoverflow.com/questions/25864559/... –  Andrei Epure 31.05.2017 в 13:18
  • Связанный (из того же OP): Как получить пользовательский тип вместо Integer при использовании Collectors.summingInt? –  Didier L 31.05.2017 в 21:18

9 ответов

11

Если вам нужен компактный код, вы можете использовать

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)));
});
    
ответ дан Holger 31.05.2017 в 13:41
источник
  • то, как вы приковали это ... потребовалось некоторое время, чтобы получить его. хороший –  Eugene 31.05.2017 в 15:38
8

Быстрая и чистая

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 31.05.2017 в 13:41
источник
  • @Holger Вы были быстрее, сортируйте ответ по старейшему. –  Alex78191 31.05.2017 в 13:50
  • @Holger i.imgur.com/KYH1cDP.png –  Alex78191 31.05.2017 в 14:03
  • 9 секунд, жизнь - это гонка :) –  Andrei Epure 31.05.2017 в 16:34
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())))));
    
ответ дан Eugene 31.05.2017 в 13:46
источник
  • Это то же самое, что и второй вариант в stackoverflow.com/a/44283598/4854931 –  Alex78191 31.05.2017 в 13:49
  • @ Alex78191 действительно, действительно близко к этому! –  Eugene 31.05.2017 в 13:50
  • Основная идея первого ответа –  Alex78191 31.05.2017 в 13:53
  • @ Alex78191 Возможно, я не смотрел на них. –  Eugene 31.05.2017 в 13:54
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));
    
ответ дан Boris the Spider 31.05.2017 в 16:10
источник
  • хорошая идея. то же самое со мной... –  holi-java 31.05.2017 в 16:13
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)
    );
}

И вы можете упростить код, так как первичная логика дублируется: адаптирует значение к другому значению в 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();
    
ответ дан holi-java 31.05.2017 в 14:33
источник
3

Для полноты, вот версия, в которой используется Maps.transformValues :

Map<String, Map<LocalDate, Duration>> result = 
    Maps.transformValues(oldMap, m -> Maps.transformValues(m, Duration::ofSeconds));
    
ответ дан Federico Peralta Schaffner 31.05.2017 в 21:15
источник
  • Я забыл об этом. Спасибо за напоминание. Я уже реорганизую свой ответ, который кажется похожим на вас, но кто-то уже ответил на мою идею одновременно. поэтому я добавляю другое использование карты (Function <Map, Map>). –  holi-java 31.05.2017 в 22:08
  • @ holi-java привет! Я поддержал ваш ответ. Мне нравится использовать функцию для ленивого преобразования значений. –  Federico Peralta Schaffner 31.05.2017 в 22:11
  • спасибо, сэр. но я хочу сказать, что последний способ, как ваш, лучше в этом случае. –  holi-java 31.05.2017 в 22:17
  • @ holi-java о, большое спасибо! Гуава настолько практична и элегантна. Хотя это не совсем то же самое, что и в вашем ответе, потому что guava возвращает вид карты. Функции применяются лениво, т. Е. При доступе к методам отображения карты. –  Federico Peralta Schaffner 31.05.2017 в 22:24
  • да, сэр. фактическое сопоставление происходит в каждом get (?) во время выполнения, поэтому он не копирует карту на другую карту, а просто ссылается на карту в классе-оболочке. –  holi-java 31.05.2017 в 22:31
3

Если я понимаю вас правильно, вы можете увидеть следующий код:

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 31.05.2017 в 13:39
источник
3

Я предпочел бы перебирать записи вашей старой карты и передавать по внутренней карте:

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())))));
    
ответ дан Stefan Warminski 31.05.2017 в 13:30
источник
  • Основная идея второго варианта из первого ответа –  Alex78191 31.05.2017 в 13:58
  • @ Alex78191 Да, он был немного быстрее –  Stefan Warminski 31.05.2017 в 14:00
1

Вот решение StreamEx :

EntryStream.of(oldMap)
           .mapValues(v -> EntryStream.of(v).mapValues(Duration::ofSeconds).toMap())
           .toMap();
    
ответ дан user_3380739 05.06.2017 в 06:38
источник