it-swarm.com.de

Java8: HashMap <X, Y> zu HashMap <X, Z> mit Stream/Map-Reduce/Collector

Ich weiß, wie man eine einfache Java List von Y -> Z, d.

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

Nun möchte ich im Grunde dasselbe mit einer Map machen, d. H .:

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

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

Die Lösung sollte nicht auf String -> Integer beschränkt sein. Genau wie im obigen Beispiel List möchte ich jede Methode (oder Konstruktor) aufrufen.

157
Benjamin M
Map<String, String> x;
Map<String, Integer> y =
    x.entrySet().stream()
        .collect(Collectors.toMap(
            e -> e.getKey(),
            e -> Integer.parseInt(e.getValue())
        ));

Es ist nicht ganz so schön wie der Listencode. Sie können keine neuen Map.Entrys in einem map()-Aufruf erstellen, sodass die Arbeit mit dem collect()-Aufruf gemischt wird.

275
John Kugelman

Hier sind einige Variationen von Sotirios Delimanolis 'Antwort , die anfangs ziemlich gut war (+1). Folgendes berücksichtigen:

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

Ein paar Punkte hier. Erstens ist die Verwendung von Platzhaltern in den Generika; Dies macht die Funktion etwas flexibler. Ein Platzhalter wäre beispielsweise erforderlich, wenn Sie möchten, dass die Ausgabezuordnung einen Schlüssel hat, der eine Oberklasse des Schlüssels der Eingabezuordnung ist:

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

(Es gibt auch ein Beispiel für die Werte der Karte, aber das ist wirklich sehr schön, und ich gebe zu, dass die Platzhalter für Y nur in Edge-Fällen hilfreich sind.)

Ein zweiter Punkt ist, dass ich den Stream nicht über die entrySet der Eingabemaske liefe, sondern über die keySet. Dies macht den Code ein wenig sauberer, denke ich, mit dem Preis, Werte aus der Karte anstatt aus dem Karteneintrag holen zu müssen. Übrigens hatte ich anfangs key -> key als erstes Argument für toMap() und dies schlug aus irgendeinem Grund mit einem Typinferenzfehler fehl. Das Ändern in (X key) -> key hat ebenso funktioniert wie Function.identity().

Eine weitere Variante ist wie folgt:

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

Dies verwendet Map.forEach() anstelle von Streams. Dies ist noch einfacher, denke ich, weil es auf die Sammler verzichtet, die mit Karten etwas unhandlich zu verwenden sind. Der Grund ist, dass Map.forEach() den Schlüssel und den Wert als separate Parameter angibt, während der Stream nur einen Wert hat - und Sie müssen entscheiden, ob Sie den Schlüssel oder den Map-Eintrag als diesen Wert verwenden möchten. Auf der Minus-Seite fehlt dies an der reichhaltigen, strengen Güte der anderen Ansätze. :-)

30
Stuart Marks

Eine generische Lösung so

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

Beispiel

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

Muss es absolut 100% funktionell und fließend sein? Wenn nicht, wie wäre es damit, das so kurz ist, wie es nur geht:

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

( wenn Sie mit der Schande und Schuld der Kombination von Streams mit Nebenwirkungen leben können )

9
Lukas Eder

Guavas Funktion Maps.transformValues ist das, was Sie suchen, und es funktioniert gut mit Lambda-Ausdrücken:

Maps.transformValues(originalMap, val -> ...)
6
Alex Krauss

Meine StreamEx - Bibliothek, die die Standard-Stream-API erweitert, bietet eine EntryStream -Klasse, die besser zum Transformieren von Karten geeignet ist:

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();
5
Tagir Valeev

Eine Alternative, die immer zu Lernzwecken vorhanden ist, besteht darin, Ihren benutzerdefinierten Collector durch Collector.of () zu erstellen, obwohl toMap () JDK-Collector hier knapp ist (+1 hier ).

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
Ira

Wenn Sie sich nicht für die Verwendung von Drittanbieter-Bibliotheken interessieren, verfügt mein cyclops-react lib über Erweiterungen für alle JDK Collection - Typen, einschließlich Map . Wir können die Karte einfach direkt mit dem Operator 'map' transformieren (standardmäßig wirkt die Karte auf die Werte in der Karte ein).

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

mit bimap können Schlüssel und Werte gleichzeitig transformiert werden

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);
3
John McClean

Die deklarative und einfachere Lösung wäre: 

yourMutableMap.replaceAll ((key, val) -> return_value_of_bi_your_function); Beachten Sie, dass Sie Ihren Kartenstatus ändern. Dies ist möglicherweise nicht das, was Sie wollen.

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

0
Breton F.