Home > Enterprise >  Generate an Map containing an Intersection/Union of values contained in two maps Map<X,Set<Y&g
Generate an Map containing an Intersection/Union of values contained in two maps Map<X,Set<Y&g

Time:06-01

I have two map arguments: Map<X,Set<Y>> map1 and Map<X,Set<Y>> map2.

I am looking for a way to write a method that generates a new map containing an Intersection of values for keys that are present in both maps, and a Union of values for keys that are present either in map1 or map2.

In other words, for any X x if it is in the domain of map1 but not in the domain of map2, then its value will be map1.get(x). Same in the opposite case. If it is in both of them, then I would like to return a set that is the intersection of map1.get(x) and map2.get(x).

Assuming I know which class X is, this can be done with the following code:

public Map<X,Set<Y>> unifyAndIntersect(Map<X,Set<Y>> map1, Map<X,Set<Y>> map2) {
    Map<X,Set<Y>> combined = new HashMap();
    combined.putAll(map1);
    for(X x : map2.keySet()){
          if(combined.contains(x)){
                Set<Y> res = Sets.newSet();
                res.addAll(map1.get(x));
                res.retainAll(map2.get(x));
                combined.put(x,res);
          }
          else{
                combined.put(x,map2.get(x));
          }
    }
}

However, I would like to make this method generic, in a sense that it will work for any X and Y. I have tried using Object, but get an error converting from my class type to Object...

Could you advise me on what is the correct way to do this?

CodePudding user response:

In order to declare a generic method, you need to provide generic type parameters <X,Y> between method modifiers and return type (for more information, see). Without that X and Y in the map type Map<X,Set<Y>> would not be treated as generic "placeholders" for types, but as types itself and the compiler will complain that types X and Y are unknown.

Don't forget the diamond <> on the right side, while instantiating a generic class, new HashMap(); without a diamond creates a map of row type.

There's also an inconsistency in the code you've provided: if a key is present is both maps a new set would be added as a value into the resulting map, but if the key is contained only in one of them according to your code an existing set would be used. I would better to ensure that subsequent modifications of the values of the resulting map will have no impact on the state of map1 and map2 by generating a new set for every value.

public <X,Y> Map<X, Set<Y>> unifyAndIntersect(Map<X,Set<Y>> map1,
                                              Map<X,Set<Y>> map2) {
    
    Map<X, Set<Y>> combined = new HashMap<>();
    for(Map.Entry<X, Set<Y>> entry: map1.entrySet()){
        Set<Y> union = new HashSet<>(entry.getValue());
        if (map2.containsKey(entry.getKey())) {
            union.retainAll(map2.get(entry.getKey()));
        }
        combined.put(entry.getKey(), union);
    }
    return combined;
}

The same logic can be implemented using Stream API:

public <X,Y> Map<X, Set<Y>> unifyAndIntersect(Map<X,Set<Y>> map1,
                                              Map<X,Set<Y>> map2) {
    return map1.entrySet().stream()
        .map(entry -> {
            Set<Y> set2 = map2.get(entry.getKey());
            return set2 == null ? Map.entry(entry.getKey(), new HashSet(entry.getValue())) : 
                Map.entry(entry.getKey(),
                entry.getValue().stream()
                    .filter(set2::contains)
                    .collect(Collectors.toSet()));
        })
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
  • Related