slee
slee

Reputation: 491

java8 a list of map reduce by key

I have a list of maps as below.

And I want to group the entry sets by key, if a key non exist, fill 0 instead.

    List<Map<String, Double>> props = Lists.newArrayList();
    Map<String, Double> m1 =  Maps.newHashMap();
    m1.put("a", 0.1);
    m1.put("b", 0.5);
    m1.put("c", 0.6);

    Map<String, Double> m2 =  Maps.newHashMap();
    m2.put("a", 0.3);
    m2.put("d", 0.1);

    Map<String, Double> m3 = Maps.newHashMap();
    m3.put("a", 0.2);

    props.add(m1); props.add(m2); props.add(m3);

Expected result:

{a=[0.1, 0.3, 0.2], b=[0.5, 0, 0], c=[0.6,0,0], d=[0,0.1,0]}

I got an idea:

  1. find all distinct keys
  2. fill every map of missing keys with value 0
  3. groupby key, values mapping to a list

Any good ideas?

Upvotes: 4

Views: 9331

Answers (3)

Shafin Mahmud
Shafin Mahmud

Reputation: 4071

It can be done in single line lamda and stream except the case fill every map of missing keys with value 0

  Map<String, List<Double>> collect = Stream.of(m1, m2, m3)
                   .flatMap(map -> map.entrySet().stream())
                   .collect(groupingBy(
                            Map.Entry::getKey,
                            mapping(Map.Entry::getValue, toList()))
                    );

  System.out.println(collect);

And this will produce the output as

{a=[0.1, 0.3, 0.2], b=[0.5], c=[0.6], d=[0.1]}

Here static factory methods Collectors.groupingBy() is used to group the concatenated Entries of all maps by the Key (classifier) and mapping method (collector).

If its the obvious case you must to fill every map of missing keys with value 0 then there is several way of doing that. One that @abhi stated in other answer. Along with his idea I would like to do that this way

  Set<String> allKeys = Sets.newHashSet();
  props.forEach(prop -> allKeys.addAll(prop.keySet()));
  allKeys.forEach(k -> props.forEach(m -> m.putIfAbsent(k, 0.0)));

Infact this will modify your original maps with allKeys having 0 as value ifNotPresent. If this modification causes you problem you can prefer to copy your maps to have separate collections.

This will ensure your desired output

{a=[0.1, 0.3, 0.2], b=[0.5, 0.0, 0.0], c=[0.6, 0.0, 0.0], d=[0.0, 0.1, 0.0]}

Note: You can find this article interesting about Grouping using Java 8 streams

Upvotes: 6

abhi
abhi

Reputation: 4792

java 8 surely does reduce the number of lines, but not to one line of code in your case

    Set<String> keys = Sets.newHashSet();
    props.forEach(prop -> keys.addAll(prop.keySet()));

    Map<String, List<Double>> result = Maps.newHashMap();
    keys.forEach(key -> result.put(key, new ArrayList<>(Collections.nCopies(props.size(), 0D))));
    props.forEach(prop -> prop.forEach((key, value) -> result.get(key).set(props.indexOf(prop), value)));

    System.out.println(result);

Upvotes: 1

Pavlo
Pavlo

Reputation: 1197

The code is not very efficient, but it gets the job done.

    List<Map<String, Double>> props = new ArrayList<>();
    Map<String, Double> m1 =  new HashMap<>();
    m1.put("a", 0.1);
    m1.put("b", 0.5);
    m1.put("c", 0.6);

    Map<String, Double> m2 =  new HashMap<>();
    m2.put("a", 0.3);
    m2.put("d", 0.1);

    Map<String, Double> m3 = new HashMap<>();
    m3.put("a", 0.2);

    props.add(m1); 
    props.add(m2); 
    props.add(m3);

    Set<String> allKeys = new HashSet<>();

    for (Map<String, Double> prop : props) {
        allKeys.addAll(prop.keySet());
    }
    HashMap<String, ArrayList<Double>> result = new HashMap<>();

    for (Map<String, Double> prop : props) {
        for (String key : allKeys) {
            if(!result.containsKey(key)){
                result.put(key, new ArrayList<>());
            }
            if(!prop.containsKey(key)){
                prop.put(key, 0.0);
            }
            Double propValue = prop.get(key);
            result.get(key).add(propValue);
        }
    }
    System.out.println(result.toString());

You basically want to use a Set<String> then iterate through all the maps to get their keys, then when you have all the keys you need to iterate through all the maps, check if the key exists, if it doesn't then add it to the map and now you can add it to the HashMap<String, ArrayList<Double>> because you know that the key exists for this map

Upvotes: 1

Related Questions