usar operaciones metodos libreria funcionales como collection and java mapreduce java-8 java-stream collectors

operaciones - Java8: HashMap<X, Y> a HashMap<X, Z> usando Stream/Map-Reduce/Collector



stream java collect (9)

Sé cómo "transformar" una simple List Java de Y -> Z , es decir:

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

Ahora me gustaría hacer básicamente lo mismo con un mapa, es decir:

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

La solución no debe limitarse a String -> Integer . Al igual que en el ejemplo de la List anterior, me gustaría llamar a cualquier método (o constructor).



Aquí hay algunas variaciones en la respuesta de Sotirios Delimanolis , que fue bastante buena para empezar (+1). Considera lo siguiente:

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

Un par de puntos aquí. Primero está el uso de comodines en los genéricos; Esto hace que la función sea algo más flexible. Sería necesario un comodín si, por ejemplo, desea que el mapa de salida tenga una clave que sea una superclase de la clave del mapa de entrada:

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

(También hay un ejemplo para los valores del mapa, pero es realmente artificial, y admito que tener el comodín delimitado para Y solo ayuda en casos extremos).

Un segundo punto es que, en lugar de ejecutar el flujo sobre el conjunto de entrada del mapa de entrySet , lo ejecuté sobre el conjunto de keySet . Esto hace que el código sea un poco más limpio, creo, a costa de tener que extraer valores del mapa en lugar de hacerlo desde la entrada del mapa. Por cierto, inicialmente tenía key -> key como primer argumento para toMap() y esto falló con un error de inferencia de tipo por alguna razón. Cambiarlo a (X key) -> key funcionó, al igual que Function.identity() .

Todavía otra variación es la siguiente:

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

Esto usa Map.forEach() lugar de secuencias. Esto es aún más simple, creo, porque prescinde de los coleccionistas, que son algo torpes de usar con los mapas. La razón es que Map.forEach() proporciona la clave y el valor como parámetros separados, mientras que la secuencia solo tiene un valor, y debe elegir si usar la clave o la entrada del mapa como ese valor. En el lado negativo, esto carece de la bondad rica y fluida de los otros enfoques. :-)


La biblioteca My StreamEx que mejora la API de transmisión estándar proporciona una clase EntryStream que se adapta mejor para transformar mapas:

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


La función Maps.transformValues Guava es lo que está buscando, y funciona bien con expresiones lambda:

Maps.transformValues(originalMap, val -> ...)



Si no le importa usar bibliotecas de terceros, mi cyclops-react tiene extensiones para todos los tipos de colección JDK , incluido Map . Simplemente podemos transformar el mapa directamente usando el operador ''mapa'' (de forma predeterminada, el mapa actúa sobre los valores del mapa).

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

bimap se puede usar para transformar las claves y los valores al mismo tiempo

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


Una alternativa que siempre existe con fines de aprendizaje es construir su recopilador personalizado a través de Collector.of () aunque toMap () JDK collector aquí es sucinto (+1 here ).

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


Una solución genérica como esta

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

Ejemplo

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


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

No es tan bueno como el código de la lista. No puede construir nuevos Map.Entry s en una llamada map() por lo que el trabajo se mezcla con la llamada collect() .