web-dev-qa-db-fra.com

Java 8 stream Map <String, List <String>> somme des valeurs pour chaque clé

Je ne suis pas si familier avec Java 8 (toujours en cours d'apprentissage) et je cherche à voir si je pourrais trouver quelque chose d'équivalent du code ci-dessous en utilisant des flux.

Le code ci-dessous essaie principalement d'obtenir la valeur double correspondante pour chaque valeur dans String, puis la résume. Je n'ai pas pu trouver beaucoup d'aide n'importe où sur ce format. Je ne suis pas sûr que l'utilisation de flux nettoierait le code ou le rendrait plus compliqué.

// safe assumptions - String/List (Key/Value) cannot be null or empty
// inputMap --> Map<String, List<String>>

Map<String, Double> finalResult = new HashMap<>();
for (Map.Entry<String, List<String>> entry : inputMap.entrySet()) {
    Double score = 0.0;
    for (String current: entry.getValue()) {
        score += computeScore(current);
    }
    finalResult.put(entry.getKey(), score);
}

private Double computeScore(String a) { .. }
12
meso

Vous pouvez également utiliser la méthode forEach avec l'API de flux pour produire le résultat que vous recherchez.

Map<String, Double> resultSet = new HashMap<>();
inputMap.forEach((k, v) -> resultSet.put(k, v.stream()
            .mapToDouble(s -> computeScore(s)).sum()));

s -> computeScore(s) peut être modifié pour utiliser une référence de méthode, c'est-à-dire T::computeScoreT est le nom de la classe contenant computeScore.

8
Aomine
Map<String, Double> finalResult = inputMap.entrySet()
        .stream()
        .collect(Collectors.toMap(
                Entry::getKey,
                e -> e.getValue()
                      .stream()
                      .mapToDouble(str -> computeScore(str))
                      .sum()));

Au-dessus du code, itère sur la carte et crée une nouvelle carte avec les mêmes clés et avant de mettre les valeurs, il itère d'abord sur chaque valeur - qui est une liste, calcule le score en appelant computeScore() sur chaque élément de la liste, puis somme les scores collectés pour être mis dans la valeur.

11
Pankaj Singhal

Celui-ci, ça va:

Map<String, Double> finalResult = inputMap.entrySet()
    .stream()
    .map(entry -> new AbstractMap.SimpleEntry<String, Double>(   // maps each key to a new
                                                                 // Entry<String, Double>
        entry.getKey(),                                          // the same key
        entry.getValue().stream()                             
            .mapToDouble(string -> computeScore(string)).sum())) // List<String> mapped to 
                                                                 // List<Double> and summed
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue));  // collected by the same 
                                                                 // key and a newly 
                                                                 // calulcated value

La version ci-dessus pourrait être fusionnée à la méthode unique collect(..):

Map<String, Double> finalResult = inputMap.entrySet()
    .stream()
    .collect(Collectors.toMap(
         Entry::getKey,                        // keeps the same key
         entry -> entry.getValue()
                       .stream()               // List<String> -> Stream<String>
                                               // then Stream<String> -> Stream<Double>
                       .mapToDouble(string -> computeScore(string)) 
                       .sum()));               // and summed 

Les éléments clés:

  • collect(..) effectue une réduction sur les éléments en utilisant une certaine stratégie avec un Collector .
  • Entry::getKey Est un raccourci pour entry -> entry.getKey. Une fonction pour mapper la clé.
  • entry -> entry.getValue().stream() renvoie le Stream<String>
  • mapToDouble(..) renvoie le DoubleStream. Cela a une opération d'agrégation sum(..) qui résume les éléments - ensemble crée une nouvelle valeur pour la carte.
3
Nikolas

Peu importe que vous utilisiez la solution basée sur le flux ou la boucle, il serait avantageux et ajouterait de la clarté et de la structure pour extraire la boucle interne dans une méthode:

private double computeScore(Collection<String> strings) 
{
    return strings.stream().mapToDouble(this::computeScore).sum();
}

Bien sûr, cela pourrait également être implémenté à l'aide d'une boucle, mais ... c'est exactement le point: cette méthode peut maintenant être appelée, soit dans la boucle externe, soit sur les valeurs d'un flux d'entrées de carte.

La boucle ou le flux externe pourrait également être tiré dans une méthode. Dans l'exemple ci-dessous, j'ai généralisé un peu ceci: le type des clés de la carte n'a pas d'importance. Que les valeurs soient des instances List ou Collection.

Au lieu de la réponse actuellement acceptée, la solution basée sur les flux ne remplit pas ici une nouvelle carte créée manuellement. Au lieu de cela, il utilise un Collector.

(Ceci est similaire à d'autres réponses, mais je pense que la méthode extraite computeScore simplifie considérablement les lambdas autrement plutôt laides qui sont nécessaires pour les flux imbriqués)

import Java.util.Arrays;
import Java.util.Collection;
import Java.util.HashMap;
import Java.util.LinkedHashMap;
import Java.util.List;
import Java.util.Map;
import Java.util.Map.Entry;
import Java.util.stream.Collectors;

public class ToStreamOrNotToStream
{
    public static void main(String[] args)
    {
        ToStreamOrNotToStream t = new ToStreamOrNotToStream();

        Map<String, List<String>> inputMap =
            new LinkedHashMap<String, List<String>>();
        inputMap.put("A", Arrays.asList("1.0", "2.0", "3.0"));
        inputMap.put("B", Arrays.asList("2.0", "3.0", "4.0"));
        inputMap.put("C", Arrays.asList("3.0", "4.0", "5.0"));

        System.out.println("Result A: " + t.computeA(inputMap));
        System.out.println("Result B: " + t.computeB(inputMap));
    }

    private <T> Map<T, Double> computeA(
        Map<T, ? extends Collection<String>> inputMap)
    {
        Map<T, Double> finalResult = new HashMap<>();
        for (Entry<T, ? extends Collection<String>> entry : inputMap.entrySet())
        {
            double score = computeScore(entry.getValue());
            finalResult.put(entry.getKey(), score);
        }
        return finalResult;
    }

    private <T> Map<T, Double> computeB(
        Map<T, ? extends Collection<String>> inputMap)
    {
        return inputMap.entrySet().stream().collect(
            Collectors.toMap(Entry::getKey, e -> computeScore(e.getValue()))); 
    }

    private double computeScore(Collection<String> strings) 
    {
        return strings.stream().mapToDouble(this::computeScore).sum();
    }

    private double computeScore(String a)
    {
        return Double.parseDouble(a);
    }

}
0
Marco13

Je l'ai trouvé un peu plus court:

value = startDates.entrySet().stream().mapToDouble(Entry::getValue).sum();
0
Zon