web-dev-qa-db-fra.com

Existe-t-il un moyen propre (et null safe) de multiplier les valeurs d'une carte en Java?

J'ai un Map<String, Double> et que vous voulez multiplier par 2 toutes les valeurs de la carte, tout en conservant les valeurs NULL comme telles.

Je peux évidemment utiliser une boucle for pour faire cela, mais je me demandais s'il y avait une façon plus propre de le faire?

Map<String, Double> someMap = someMapFunction();
Map<String, Double> adjustedMap = new Hashmap<>();
if (someMap != null) {
    for (Map.Entry<String,Double> pair : someMap.entryset()) {
        if (pair.getValue() == null) {
            adjustedMap.put(pair.getKey(), pair.getValue());
        } else {
            adjustedMap.put(pair.getKey(), pair.getValue()*2)
        }

    }
}

De plus, parfois, la carte renvoyée par someMapFunction est une carte immuable, ce qui ne peut donc pas être fait en place avec Map.replaceAll. Je ne pouvais pas proposer une solution de flux plus propre.

48
Omar Haque

Mon premier instinct a été de suggérer un Stream de l'entrée Map de entrySet qui mappe les valeurs sur de nouvelles valeurs et se termine par collectors.toMap().

Malheureusement, Collectors.toMap jette NullPointerException lorsque la fonction mappeur de valeur renvoie null. Par conséquent, cela ne fonctionne pas avec les valeurs null de votre entrée Map.

Comme alternative, puisque vous ne pouvez pas muter votre entrée Map, je vous suggère de créer une copie de celle-ci, puis appelez replaceAll:

Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.replaceAll ((k,v) -> v != null ? 2*v : null);
61
Eran

En guise d'alternative aux solutions de diffusion en continu et/ou de copie, la méthode de l'utilitaire Maps.transformValues() existe dans Google Guava :

Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);

Cela renvoie une vue paresseuse de la carte d'origine qui ne fait aucun travail à part, mais applique la fonction donnée si nécessaire. Cela peut être à la fois un pro (s'il est peu probable que vous ayez jamais besoin de toutes les valeurs, cela vous fera économiser un peu de temps de calcul) et un inconvénient (si vous avez besoin de la même valeur plusieurs fois, ou si vous devez encore changer someMap sans adjustedMap voir les changements) en fonction de votre utilisation.

25
Petr Janeček

Il y a déjà beaucoup de réponses. Certains me semblent un peu douteux. Dans tous les cas, la plupart d'entre eux sont intégrés au chèque null- sous une forme ou une autre.

Une approche qui monte d'un échelon sur l'échelle d'abstraction est la suivante:

Vous souhaitez appliquer un opérateur unaire aux valeurs de la carte. Vous pouvez donc implémenter une méthode qui applique un opérateur unaire aux valeurs de la carte. (Jusqu'ici tout va bien). Maintenant, vous voulez un opérateur unaire "spécial" qui soit null-safe. Ensuite, vous pouvez envelopper un opérateur unaire sûr null- en toute sécurité.

Ceci est montré ici, avec trois opérateurs différents (l’un d’eux étant Math::sin, d'ailleurs) :

import Java.util.LinkedHashMap;
import Java.util.Map;
import Java.util.Map.Entry;
import Java.util.function.UnaryOperator;

public class MapValueOps
{
    public static void main(String[] args)
    {
        Map<String, Double> map = new LinkedHashMap<String, Double>();
        map.put("A", 1.2);
        map.put("B", 2.3);
        map.put("C", null);
        map.put("D", 4.5);

        Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2));
        System.out.println(resultA);

        Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2));
        System.out.println(resultB);

        Map<String, Double> resultC = apply(map, nullSafe(Math::sin));
        System.out.println(resultC);

    }

    private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op)
    {
        return t -> (t == null ? t : op.apply(t));
    }

    private static <K> Map<K, Double> apply(
        Map<K, Double> map, UnaryOperator<Double> op)
    {
        Map<K, Double> result = new LinkedHashMap<K, Double>();
        for (Entry<K, Double> entry : map.entrySet())
        {
            result.put(entry.getKey(), op.apply(entry.getValue()));
        }
        return result;
    }
}

Je pense que c'est propre, parce que cela sépare bien les problèmes d'application de l'opérateur et de vérification null-. Et c'est null- sûr, parce que ... le nom de la méthode le dit.

(On pourrait argumenter en tirant l'appel pour envelopper l'opérateur dans une méthode nullSafe dans la méthode apply, mais ce n'est pas le point ici)

Modifier:

En fonction du modèle d'application souhaité, vous pouvez faire quelque chose de similaire et appliquer la transformation à la place , sans créer de nouvelle carte, en appelant Map#replaceAll

6
Marco13

Vous pouvez y parvenir en convertissant en flux, avec quelque chose comme:

someMap.entrySet()
        .forEach(entry -> {
            if (entry.getValue() != null) {
                adjustedMap.put(entry.getKey(), someMap.get(entry.getKey()) * 2);
            } else {
                adjustedMap.put(entry.getKey(), null);
            }
        });

qui peut être réduit à:

someMap.forEach((key, value) -> {
    if (value != null) {
        adjustedMap.put(key, value * 2);
    } else {
        adjustedMap.put(key, null);
    }
});

Donc, si vous avez une carte avec:

Map<String, Double> someMap = new HashMap<>();
someMap.put("test1", 1d);
someMap.put("test2", 2d);
someMap.put("test3", 3d);
someMap.put("testNull", null);
someMap.put("test4", 4d);

Vous obtiendrez cette sortie:

{test4=8.0, test2=4.0, test3=6.0, testNull=null, test1=2.0}
5
Leviand

Que dis-tu de ça?

Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
      if (x.getValue() != null) {
            x.setValue(x.getValue() * 2);
      }
});
4
Eugene

Ça peut se faire comme ça

someMap.entrySet().stream()
            .filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out
            .forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null

Au cas où vous auriez besoin d’une deuxième carte dans laquelle seules les valeurs non nulles utiliseraient le forEach pour les insérer dans votre nouvelle carte.

4
CodeMatrix

Vous pouvez le faire avec ce code:

Map<String, Double> map = new HashMap<>();
map.put("1", 3.0);
map.put("3", null);
map.put("2", 5.0);

Map<String, Double> res = 
map.entrySet()
   .stream()
   .collect(
           HashMap::new, 
           (m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null),
           HashMap::putAll
           );

System.out.println(res);

et le résultat sera:

{1 = 6,0, 2 = 10,0, 3 = nul}

Cela vous permettra de garder null valeurs dans la carte.

3
user987339

Encore une autre manière:

Map<String, Double> someMap = someMapFunction();

int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1);
Map<String, Double> adjustedMap = new HashMap<>(capacity);

if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));

Notez que je construis la nouvelle carte avec le facteur de charge par défaut (0.75 = 3.0 / 4.0) et une capacité initiale toujours supérieure à size * load_factor. Cela garantit que adjustedMap ne sera jamais redimensionné/redécoré.

Pour conserver les valeurs NULL, vous pouvez utiliser quelque chose d'aussi simple que:

someMap.keySet()
        .stream()
        .forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));

Editez en réponse à commentaire Petr Janeček : vous pouvez appliquer la proposition sur une copie de someMap:

adjustedMap.putAll(someMap);
adjustedMap.keySet()
       .stream()
       .forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));
2
c0der

Si vous êtes d'accord avec les valeurs Optional, ce qui suit peut vous convenir:

import Java.util.HashMap;
import Java.util.Map;
import Java.util.Optional;
import Java.util.function.Function;

import static Java.util.stream.Collectors.toMap;


public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) {
    return map.entrySet().stream().collect(
            toMap(
                    e -> e.getKey(),
                    e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue()))
            ));
}

puis:

public static void main(String[] args) throws Exception {
    Map<String, Double> map = new HashMap<>();
    map.put("a", 2.0);
    map.put("b", null);
    map.put("c", 3.0);


    System.out.println(g(map, x -> x * 2));
    System.out.println(g(map, x -> Math.sin(x)));
}

impressions:

{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]}
{a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}

Ceci est assez propre avec la création de la nouvelle carte déléguée à Collectors et l’avantage supplémentaire du type de retour Map<String, Optional<Double>> indiquant clairement la possibilité nulls et encourageant les utilisateurs à les gérer.

1
David Soroko