简体   繁体   中英

Java Stream compare and filter entries from Map<String, Map<String, List<String>>>

I have the following class:

 public class SomeObject {

 private String id;
 private String parentId;
 private String type;
 
 //constructor,getters,setters
 }

And the following use case:

The field values are not unique. I have a List of SomeObject. First I want to know which SomeOjects share the same parentId and secondly which of those share the same type. First I wanted to group them into the following structure:

 Map<String, Map<String, List<String>>>

The key of the first map is the parentId and the value is another map. The key of the second map is the type and the value of the second map is a list of ids from the SomeObjects.

I was able to do this as follows:

    Map<String, Map<String, List<String>>> firstTry =
    SomeObjects.stream()
        .collect(
            groupingBy(
                SomeObject::getParentId,
                groupingBy(
                    SomeObject::getType,
                    mapping(SomeObject::getId, toList()))));

And now comes the part where I need some help:

I now want to filter this created map as follows:

Lets assume I have 3 parentId keys which each then have a map with two keys: type1 and type2. (and 2 lists of ids as values)

If the list of ids from type2 contains more/less/different ids than the list of ids from type1, then I want to delete/filter out their parentId entry. And I want to do that for each parentId.

Is there any way with streams to cleanly achieve this?

Some points I would use to improve the code before the answere:

  • I would leave the SomeObject pointer instead of the String literal in the map. Not only they are gonna be more memory efficient most of the time (8 bytes fixed vs 2*character bytes String) but also much more convenient to access the data.

  • I would also make the type String an enum type.

But getting to the core of the question, i'm sorry.

Lets assume I have 3 parentId keys which each then have a map with two keys: type1 and type2. (and 2 lists of ids as values)

{
    'p1': {
         'type1':['1','2','uuid-random-whatever'],
         'type2':['asdsad']
    },
    'p2': {
         'type1':['1','2'],
         'type2':['asdsad','i','want','more','power']
    },
    'p3': {
         'type1':['2'],
         'type2':['2']
    }
}
    

Something like this

So for the filtering itself

        Map<String, Map<String, List<String>>> theMap
                = buildMapExample();

        Predicate<Map.Entry<String, Map<String, List<String>>>> filterType2LessElementsType1 =
                (Map.Entry<String, Map<String, List<String>>> entry) ->
                        entry.getValue().get("type2").size() < entry.getValue().get("type1").size();

        Predicate<Map.Entry<String, Map<String, List<String>>>> filterType2MoreElementsType1 =
                (Map.Entry<String, Map<String, List<String>>> entry) ->
                        entry.getValue().get("type2").size() > entry.getValue().get("type1").size();

        Predicate<Map.Entry<String, Map<String, List<String>>>> filterType2SameElementsType1 =
                (Map.Entry<String, Map<String, List<String>>> entry) ->
                        (new HashSet<>(entry.getValue().get("type2")))
                                .equals(new HashSet<>(entry.getValue().get("type1")));

        theMap = theMap.entrySet().stream()
                .filter(
                        // Choose your lambda for more/less/different. for example
                        filterType2LessElementsType1
                )
                .collect(
                        Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)
                );

        printMap(theMap);

This code would work. I left out the building/printing to not make this larger as it should. Try it out

If you need to retain only those parentId which have the same ids per type, it can be done by converting lists of ids into set and checking the set size:

List<SomeObject> list = Arrays.asList(
    new SomeObject("id1", "p1", "type1"),
    new SomeObject("id1", "p1", "type2"),

    new SomeObject("id2", "p2", "type1"),
    new SomeObject("id3", "p2", "type1"),
    new SomeObject("id2", "p2", "type2"),
            
    new SomeObject("id4", "p3", "type1"),
    new SomeObject("id4", "p3", "type2"),
    new SomeObject("id5", "p3", "type2")
);
//.. building firstTry as in the initial code snippet

System.out.println(firstTry);

firstTry.entrySet().stream()
        .filter(e -> new HashSet(e.getValue().values()).size() == 1)
        .forEach(System.out::println);

Output:

{p1={type2=[id1], type1=[id1]}, p2={type2=[id2], type1=[id2, id3]}, p3={type2=[id4, id5], type1=[id4]}}
p1={type2=[id1], type1=[id1]}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM