Java8: HashMap to HashMap using Stream / Map-Reduce / Collector

Wiem jak "przekształcić" prostą Javę List z Y -> Z, tj.:

List<String> x;
List<Integer> y = x.stream()
        .map(s -> Integer.parseInt(s))
        .collect(Collectors.toList());

Teraz chciałbym zrobić w zasadzie to samo z mapą, czyli:

INPUT:
{
  "key1" -> "41",    // "41" and "42"
  "key2" -> "42      // are Strings
}

OUTPUT:
{
  "key1" -> 41,      // 41 and 42
  "key2" -> 42       // are Integers
}

Rozwiązanie nie powinno ograniczać się do String -> Integer. Podobnie jak w powyższym przykładzie List, chciałbym wywołać dowolną metodę (lub konstruktor).

Author: ROMANIA_engineer, 2014-09-18

9 answers

Map<String, String> x;
Map<String, Integer> y =
    x.entrySet().stream()
        .collect(Collectors.toMap(
            e -> e.getKey(),
            e -> Integer.parseInt(e.getValue())
        ));
Nie jest tak ładny jak kod listy. Nie można konstruować nowych Map.Entrys w wywołaniu map(), więc praca jest mieszana z wywołaniem collect().
 252
Author: John Kugelman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-06 14:55:33

Oto kilka wariacji na temat odpowiedzi Sotiriosa Delimanolisa , która była całkiem dobra na początek (+1). Rozważmy następujące:

static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
                                     Function<Y, Z> function) {
    return input.keySet().stream()
        .collect(Collectors.toMap(Function.identity(),
                                  key -> function.apply(input.get(key))));
}

Kilka punktów tutaj. Pierwszym jest użycie symboli wieloznacznych w generykach; to sprawia, że funkcja jest nieco bardziej elastyczna. Symbol wieloznaczny byłby konieczny, jeśli na przykład chcesz, aby Mapa wyjściowa miała klucz, który jest superklasą klucza mapy wejściowej:

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);

(jest też przykład wartości mapy, ale to naprawdę i przyznam, że posiadanie tzw. wildcard dla Y pomaga tylko w przypadkach skrajnych.)

Druga kwestia jest taka, że zamiast uruchamiać strumień na mapie wejściowej entrySet, przepuściłem go na keySet. To sprawia, że kod jest trochę czystszy, moim zdaniem, kosztem konieczności pobierania wartości z mapy zamiast z wpisu mapy. Nawiasem mówiąc, początkowo miałem key -> key jako pierwszy argument toMap() i to nie powiodło się z błędem wnioskowania typu z jakiegoś powodu. Zmiana na (X key) -> key zadziałała, jak did Function.identity().

Jeszcze inna odmiana jest następująca:

static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
                                      Function<Y, Z> function) {
    Map<X, Z> result = new HashMap<>();
    input.forEach((k, v) -> result.put(k, function.apply(v)));
    return result;
}

To używa Map.forEach() zamiast strumieni. Wydaje mi się, że jest to jeszcze prostsze, ponieważ rezygnuje z kolekcjonerów, które są nieco niezdarne w użyciu z mapami. Powodem jest to, że Map.forEach() podaje klucz i wartość jako oddzielne parametry, podczas gdy strumień ma tylko jedną wartość - i musisz wybrać, czy użyć klucza, czy wpisu mapy jako tej wartości. Z drugiej strony, brakuje w tym bogatej, strumieniowej dobroci drugiej zbliża się. :-)

 26
Author: Stuart Marks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:10

Takie ogólne rozwiązanie

public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
        Function<Y, Z> function) {
    return input
            .entrySet()
            .stream()
            .collect(
                    Collectors.toMap((entry) -> entry.getKey(),
                            (entry) -> function.apply(entry.getValue())));
}

Przykład

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
            (val) -> Integer.parseInt(val));
 11
Author: Sotirios Delimanolis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-18 02:23:51

Czy absolutnie musi być w 100% funkcjonalny i płynny? Jeśli nie, to jak o tym, który jest o tak krótki, jak to się robi:

Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));

(jeśli możesz żyć ze wstydem i winą łączenia strumieni z efektami ubocznymi )

 7
Author: Lukas Eder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-28 16:10:28

Moja StreamEx biblioteka rozszerzająca standardowe API strumienia zapewnia EntryStream klasa, która lepiej nadaje się do przekształcania map:

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
 5
Author: Tagir Valeev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-29 04:52:04

Funkcja guawa Maps.transformValues jest tym, czego szukasz, i działa dobrze z wyrażeniami lambda:

Maps.transformValues(originalMap, val -> ...)
 5
Author: Alex Krauss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-05 22:19:13

Jeśli nie masz nic przeciwko używaniu bibliotek stron trzecich, my cyclops-react lib ma rozszerzenia dla wszystkich typów JDK Collection , w tym Map . Możemy po prostu przekształcić mapę bezpośrednio za pomocą operatora 'map' (domyślnie Mapa działa na wartości na mapie).

   MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                                .map(Integer::parseInt);

Bimap może być używany do przekształcania kluczy i wartości w tym samym czasie

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);
 3
Author: John McClean,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-23 12:45:22

Alternatywą, która zawsze istnieje dla celów edukacyjnych, jest zbudowanie niestandardowego kolektora za pomocą kolektora.z () choć toMap() JDK jest tutaj zwięzły (+1 Tutaj ).

Map<String,Integer> newMap = givenMap.
                entrySet().
                stream().collect(Collector.of
               ( ()-> new HashMap<String,Integer>(),
                       (mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
                       (map1,map2)->{ map1.putAll(map2); return map1;}
               ));
 3
Author: Ira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:56

Deklaratywnym i prostszym rozwiązaniem byłoby:

YourMutableMap.replaceAll ((key, val) - > return_value_of_bi_your_function); Nb. należy pamiętać o modyfikowaniu stanu mapy. Więc to może nie być to, czego chcesz.

Pozdrawiam : http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

 0
Author: Breton F.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-10 13:22:57