2015-09-14 2 views
1

У меня есть Map следующего типаСреднее количество баллов из HashMap с использованием java 8 stream API?

public class MapUtils { 

    private Map<String, Integer> queryCounts = new HashMap<>(); 

public void averageCounters(){ 

    int totalCounts = queryCounts.values().stream().reduce(0, Integer::sum); 
    queryCounts = queryCounts.entrySet() 
          .stream() 
          .collect(Collectors.toMap(
           Map.Entry::getKey, 
           (Map.Entry::getValue)/totalCounts 
    )); 
} 

Это не компилировать и показывает ошибку в этой строке (Map.Entry::getValue)/totalCounts. Как это исправить? Есть ли лучший способ добиться среднего значения по сравнению с Map с использованием Java 8 API?

EDIT: Это лучший подход?

queryCounts.entrySet() 
      .forEach(entry -> queryCounts.put(entry.getKey(), 
            entry.getValue()/totalCounts)); 

ответ

9

Если вы хотите в месте изменения, это гораздо лучше использовать Map.replaceAll вместо потока API:

int totalCounts = queryCounts.values().stream() 
          .collect(Collectors.summingInt(Integer::intValue)); 
queryCounts.replaceAll((k, v) -> v/totalCounts); 

Однако в вашем случае это решение является проблематичным, так как результаты деления будут округлены до int число, таким образом, вы почти всегда получите нули в результате. На самом деле в коде есть одна и та же проблема. Вероятно, вы хотите получить Map<String, Double> в качестве результата. Таким образом, вы, вероятно, нужно, чтобы создать совершенно новый Map:

Map<String, Double> averages = queryCounts.entrySet().stream() 
              .collect(Collectors.toMap(Entry::getKey, 
               e -> ((double)e.getValue())/totalCounts)); 

Альтернативой будет иметь queryCounts объявлен Map<String, Double> в первую очередь. Таким образом, вы можете использовать replaceAll:

double totalCounts = queryCounts.values().stream() 
          .collect(Collectors.summingDouble(Double::doubleValue)); 
queryCounts.replaceAll((k, v) -> v/totalCounts); 

Наконец, есть еще одна альтернатива, которая является наиболее эффективным, но грязный. Ваш код предполагает, что оригинальные (не усредненные) queryCounts не нужны после вызова averageCounters(). Таким образом, вы можете сохранить queryCounts в Map<String, Integer> (который является более эффективным, чем рассчитывающие Map<String, Double>), но затем изменить Map значения типа как это:

double totalCounts = queryCounts.values().stream() 
          .collect(Collectors.summingInt(Integer::intValue)); 
Map<String, Object> map = (Map<String, Object>)queryCounts; 
map.replaceAll((k, v) -> ((Integer)v)/totalCounts); 
Map<String, Double> averages = (Map<String, Double>)map; 
queryCounts = null; 

Подобный трюк в JDK performed внутри Collectors.groupingBy реализации.

Смежные вопросы