Finding average using Lambda (Double stored as Str

2019-04-13 15:26发布

问题:

I have an object of the following format

Map<String, List<ObjectDTO>> mapOfIndicators = new HashMap<>();

ObjectDTO is

public class ObjectDTO {
 private static final long   serialVersionUID = 1L;

    private String iName;
    private String dName;
    private String countryName;
    private int year;

    //Since the value can be empty, using String instead of Double
    private String newIndex;
}

I am trying to compute the average of the newIndex value, which basically I cannot do as getNewIndex is String, and there can be empty string(values). I cannot convert to the null values to 0 as there will be 0 values in newIndex.

Is there an easier way to calculate the average?

Eg.

mapOfIndicators = new HashMap<>();
List<IndicatorTableDTO> _tempValue = new ArrayList();
IndicatorTableDTO _iTDTO = new IndicatorTableDTO("iName", "dName", "countryName1", 2012, "1.00");
_tempValue.add(_iTDTO);
_iTDTO = new IndicatorTableDTO("iName", "dName", "countryName2", 2012, "");
_tempValue.add(_iTDTO);
_iTDTO = new IndicatorTableDTO("iName", "dName", "countryName3", 2012, "0.02");
_tempValue.add(_iTDTO);
_iTDTO = new IndicatorTableDTO("iName", "dName", "countryName4", 2012, "-0.25");
_tempValue.add(_iTDTO);
_iTDTO = new IndicatorTableDTO("iName", "dName", "countryName5", 2012, "0.10");
_tempValue.add(_iTDTO);

mapOfIndicators.put("Test1", _tempValue);

   _iTDTO = new IndicatorTableDTO("iName", "dName", "countryName1", 2012, "0.10");
   _tempValue.add(_iTDTO);
   _iTDTO = new IndicatorTableDTO("iName", "dName", "countryName2", 2012, "", "", "");
   _tempValue.add(_iTDTO);
   _iTDTO = new IndicatorTableDTO("iName", "dName", "countryName3", 2012, "", "", "");
   _tempValue.add(_iTDTO);
   _iTDTO = new IndicatorTableDTO("iName", "dName", "countryName4", 2012, "0.25", "", "");
   _tempValue.add(_iTDTO);
   _iTDTO = new IndicatorTableDTO("iName", "dName", "countryName5", 2012, "1.10", "", "");
   _tempValue.add(_iTDTO);

   mapOfIndicators.put("Test2", _tempValue);

Update 01: We can skip empty string/null values; the average would be per countryName, so I want to find the average of the newIndex for same contryName and eventually be able to get a Map as the final result.

回答1:

Not tested, but should do the trick (or at least guide you towards the solution):

Map<String, Double> averageByCountryName = 
    map.values()
       .stream()
       .flatMap(list -> list.stream())
       .filter(objectDTO -> !objectDTO.getNewIndex().isEmpty())
       .collect(Collectors.groupingBy(
            ObjectDTO::getCountry,
            Collectors.averagingDouble(
                objectDTO -> Double.parseDouble(objectDTO.getNewIndex())));


回答2:

If you want to calculate the average of the values and collect them in a Map having the same keys as the source map, you can do it this way:

Map<String, Double> averages=mapOfIndicators.entrySet().stream()
    .collect(Collectors.toMap(Map.Entry::getKey, e->e.getValue().stream()
       .map(ObjectDTO::getNewIndex).filter(str->!str.isEmpty())
       .mapToDouble(Double::parseDouble).average().orElse(Double.NaN)));

Since ObjectDTO.newIndex is declared private, I assumed that there is a method getNewIndex to get its value. I also provided the default value Double.NaN as behavior for the case that there are no non-empty Strings in the value list which was not specified in the question.


In the case you want to average over a different key as with your question now defined more precisely, the code may look like:

Map<String, Double> averages=mapOfIndicators.values().stream()
    .flatMap(Collection::stream)
    .filter(objectDTO -> !objectDTO.getNewIndex().isEmpty())
    .collect(Collectors.groupingBy(ObjectDTO::getCountryName,
        Collectors.mapping(ObjectDTO::getNewIndex,
            Collectors.averagingDouble(Double::parseDouble))));

Well, that’s similar to JB Nizet’s answer which got your intention right on the first guess…