Java8 How to convert 3-level nested list into nested HashMap using stream and lambda

Viewed 757

I'm trying to convert a 3 level nested list into Nested HashMap.

The function declaration for the same is:

Map<Key1, Map<Key2, List<String>>> transformToMap (List<Obj1> inputList)

The inputList internally has nested list which again has nested list.

The code I've wrote is using traditional for loop as follow:

private Map<Key1 , Map<Key2, List<String>>> reverseLookup(List<Key2> key2List){
    Map<Key1 , Map<Key2, List<String>>>  resultMap = new HashMap<>();
    key2List.forEach(key2->{
        List<ElementObject> elementObjects = key2.getElementObjects();
        elementObjects.forEach(elementObject->{
            final String name = elementObject.getName();
            elementObject.getApplicablePeriods().forEach(applicablePeriod-> {
                Key1 key1 = applicablePeriod.getKey1();
                Map<Key2, List<String>> map2 = resultMap.get(key1);
                if(map2 == null){
                    map2 = new HashMap<>();
                }
                List<String> stringList = map2.get(key2);
                if(stringList == null){
                    stringList = new ArrayList<>();
                }
                stringList.add(name);
                map2.put(key2, stringList);
                resultMap.put(key1, map2);
            });
        });
    });

    return resultMap;
}

The class structure for the same is as follow:

class Key2{

    List<ElementObject> elementObjects;

    //getters & setters
}

class ElementObject {
    String name;
    //few more params
    List<ApplicablePeriod> applicablePeriods;
     //getters & setters
}

class ApplicablePeriod{
    Key1 key1;
    //getters & setters
}

class Key1{
    //some parameters
    //getters & setters
}

The above code is fulfilling my expectations.

What will be the efficient way to transform it into stream lambda using Collectors.toMap ?

I've tried something as follow:

inputList
        .stream()
        .flatMap(item -> item.getObj2List().stream())
        .flatMap(nestedItem -> nestedItem.getKeyList().stream())
        .collect(Collectors.toMap(a-> a.get()))

But not getting what should be the next step in Collectors.toMap. Not able to handle final String name = nestedItem.getName(); which is used just before 3rd for loop.

Let me know the way to solve this.

2 Answers

I don't have any test data to see if it creates something similar as your traditional code. But take a look at this and let me know if it helps:

key2List.stream().flatMap((key2) -> key2.elementObjects.stream().map((element) -> new AbstractMap.SimpleImmutableEntry<>(key2, element)))
    .flatMap((entry) -> entry.getValue().applicablePeriods.stream().map((period) -> new AbstractMap.SimpleImmutableEntry<>(period.key1, new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue().name))))
    .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())))));

In fact, the problem is that you want to access multiple levels of abstractions inside of the same Stream, this is usually not possible unless you have

  • Nested streams
  • An object that can hold references to higher objects

I fixed it in the second way.

I'm using and the following features

  • Collectors#mapMulti
  • Collectors#groupingBy
  • A locally defined record
private Map<Key1, Map<Key2, List<String>>> reverseLookup(List<Key2> key2List) {
    record HolderObject(Key2 key2, ElementObject elementObject, ApplicablePeriod applicablePeriod){}

    return key2List.stream()
            .mapMulti((Key2 key2, Consumer<HolderObject> consumer) -> {
                List<ElementObject> elementObjects = key2.getElementObjects();
                elementObjects.forEach(elementObject ->
                        elementObject.getApplicablePeriods().forEach(applicablePeriod -> {
                            consumer.accept(new HolderObject(
                                    key2,
                                    elementObject,
                                    applicablePeriod
                            ));
                        })
                );
            })
            .collect(Collectors.groupingBy(
                    h -> h.applicablePeriod().getKey1(),
                    Collectors.groupingBy(
                            HolderObject::key2,
                            Collectors.mapping(
                                    h -> h.elementObject().getName(),
                                    Collectors.toList()
                            )
                    )
            ));
}

And here is a compatible solution

private Map<Key1, Map<Key2, List<String>>> reverseLookup(List<Key2> key2List) {
    return key2List
            .stream()
            .flatMap(key2 -> key2.getElementObjects()
                    .stream()
                    .flatMap(elementObject -> elementObject.getApplicablePeriods()
                            .stream()
                            .map(applicablePeriod -> new HolderObject(
                                    key2,
                                    elementObject,
                                    applicablePeriod
                            ))))
            .collect(Collectors.groupingBy(
                    h -> h.getApplicablePeriod().getKey1(),
                    Collectors.groupingBy(
                            HolderObject::getKey2,
                            Collectors.mapping(
                                    h -> h.getElementObject().getName(),
                                    Collectors.toList()
                            )
                    )
            ));
}

@Value
public static class HolderObject {
    Key2 key2;
    ElementObject elementObject;
    ApplicablePeriod applicablePeriod;
}
Related