Карта потока Java 8 <String, List<string> > сумма значений для каждого ключа

Я не очень хорошо знаком с Java 8 (все еще учится) и смотрю, могу ли я найти что-то эквивалентное приведенному ниже коду с использованием потоков.

Приведенный ниже код в основном пытается получить соответствующее двойное значение для каждого значения в String, а затем суммирует его. В этом формате я не мог найти никакой помощи. Я не уверен, что использование потоков очистит код или сделает его более беспорядочным.

// 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) { .. }

Ответы

Ответ 1

Вы также можете использовать метод forEach вместе с API потока, чтобы получить результат, который вы ищете.

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

s → computeScore(s) можно изменить, чтобы использовать ссылку на метод, т.е. T::computeScore где T - это имя класса, содержащего computeScore.

Ответ 2

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

Выше код выполняет итерацию по карте и создает новую карту с теми же ключами и перед тем, как поместить значения, она сначала выполняет итерацию по каждому значению - это список, вычисляет счет через вызов computeScore() по каждому элементу списка, а затем суммирует собранные оценки быть введено в значение.

Ответ 3

Как насчет этого:

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

Вышеупомянутая версия может быть объединена с методом single 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 

Основные части:

  • collect(..) выполняет уменьшение элементов, используя определенную стратегию с Collector.
  • Entry::getKey - это ярлык для entry → entry.getKey. Функция для отображения ключа.
  • entry → entry.getValue().stream() возвращает Stream<String>
  • mapToDouble(..) возвращает DoubleStream. У этого есть суммарная sum(..) операции sum(..) которая суммирует элементы - вместе создает новое значение для Карты.

Ответ 4

Независимо от того, используете ли вы потоковое решение или решение на основе цикла, было бы полезно и добавить некоторую ясность и структуру для извлечения внутреннего цикла в метод:

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

Конечно, это также можно реализовать с помощью цикла, но... что именно точка: этот метод теперь можно вызывать либо во внешнем цикле, либо в значениях потока записей карты.

Внешний цикл или поток также можно было бы втянуть в метод. В приведенном ниже примере я немного обобщил это: тип ключей карты не имеет значения. Также значения не являются экземплярами List или Collection.

В качестве альтернативы принятому в настоящее время ответу, основанное на потоке решение здесь не заполняет новую карту, созданную вручную. Вместо этого он использует Collector.

(Это похоже на другие ответы, но я думаю, что извлеченный метод computeScore значительно упрощает иначе уродливые лямбды, которые необходимы для вложенных потоков)

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

}

Ответ 5

Я нашел это несколько короче:

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