web-dev-qa-db-fra.com

Java8: HashMap <X, Y> à HashMap <X, Z> à l'aide de Stream/Map-réduire/Collector

Je sais comment "transformer" un simple fichier Java List de Y -> Z,

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

Maintenant, j'aimerais faire la même chose avec une carte, c'est-à-dire:

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

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

La solution ne doit pas être limitée à String -> Integer. Comme dans l'exemple List ci-dessus, j'aimerais appeler n'importe quelle méthode (ou constructeur).

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

Ce n'est pas aussi beau que le code de la liste. Vous ne pouvez pas créer de nouveaux Map.Entrys dans un appel map(), le travail est donc mélangé à l'appel collect().

275
John Kugelman

Voici quelques variations sur La réponse de Sotirios Delimanolis , qui était plutôt bonne au début (+1). Considérer ce qui suit:

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

Quelques points ici. Le premier est l'utilisation de caractères génériques dans les génériques; cela rend la fonction un peu plus flexible. Un caractère générique est nécessaire si, par exemple, vous voulez que la carte en sortie ait une clé qui est une superclasse de la clé de la carte en entrée:

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

(Il y a aussi un exemple pour les valeurs de la carte, mais c'est vraiment artificiel, et j'avoue que le fait d'utiliser le caractère générique lié pour Y n'est utile que dans les cas Edge.)

Un deuxième point est qu'au lieu d'exécuter le flux sur la entrySet de la mappe d'entrée, je l'ai exécuté sur la keySet. Cela rend le code un peu plus propre, je pense, au prix de devoir extraire des valeurs de la carte plutôt que de l'entrée de la carte. Incidemment, j'avais initialement key -> key comme premier argument de toMap() et cela a échoué avec une erreur d'inférence de type pour une raison quelconque. Le changer en (X key) -> key a fonctionné, de même que Function.identity().

Encore une autre variante est la suivante:

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

Ceci utilise Map.forEach() à la place des flux. C'est encore plus simple, je pense, car cela permet de se passer des collectionneurs, qui sont un peu maladroits à utiliser avec des cartes. La raison en est que Map.forEach() donne la clé et la valeur sous forme de paramètres distincts, alors que le flux n'a qu'une valeur - et vous devez choisir d'utiliser la clé ou l'entrée de la carte en tant que valeur. Sur le plan négatif, cela manque de la richesse et de la bonté des autres approches. :-)

30
Stuart Marks

Une solution générique comme ça

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

Exemple

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

Doit-il absolument être 100% fonctionnel et fluide? Si non, que diriez-vous de cela, qui est à peu près aussi court que cela devient:

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

( Si vous pouvez vivre avec la honte et la culpabilité de combiner des cours d'eau avec des effets secondaires )

9
Lukas Eder

La fonction de Guava Maps.transformValues est ce que vous recherchez, et elle fonctionne bien avec les expressions lambda:

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

Mon StreamEx bibliothèque qui améliore l'API de flux standard fournit une classe EntryStream qui convient mieux à la transformation de cartes

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

Une alternative qui existe toujours à des fins d’apprentissage est de construire votre collecteur personnalisé via Collector.of () bien que le collecteur toMap () JDK soit succinct (+1 ici ).

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

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces, mon cyclops-react lib a des extensions pour tous les JDK Collection types, y compris Map . Nous pouvons simplement transformer la carte directement en utilisant l'opérateur 'map' (par défaut, map agit sur les valeurs de la carte).

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

bimap peut être utilisé pour transformer les clés et les valeurs en même temps

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

La solution déclarative et plus simple serait: 

votreMutableMap.replaceAll ((key, val) -> return_value_of_bi_y_function);; sachez que vous modifiez l'état de votre carte. Donc, ce n'est peut-être pas ce que vous voulez.

Cheers to: http://www.deadcoderising.com/2017-02-14-Java-8-declarative-ways-of-modifying-a-map-using-compute-merge-re-replace /

0
Breton F.