3

What I would like to do is group elements of a List in order to create a map, based on specific fields. The desired output is the following: Map<String,Map<String,Map<String,MasterObject>>>. The order of the keys is date, type, color.

My code is as follows

public class MasterObject{
   private String date;
   private List<SubObject> subObject;
}

public class SubObject{
   private String type;
   private String color;
}

What I have tried and used is Collectors.groupingBy which works great if your fields are members of the same Object (ex. date), but haven't managed to make it work for containing objects (ex. subObject). My input is a List<MasterObject>. It could be done the hard way by using Map's put and get but maybe there is a much cleaner way to make it work with Java streams.

What I have tried thus far is the following:

    Map<String, Map<List<String>, List<MasterObject>>> collect = faList.stream().collect(
    Collectors.groupingBy(f -> f.getDate(),
    Collectors.groupingBy(f -> f.getSubObject().stream().map(z -> z.getType()).collect(Collectors.toList()))));

In my sample above, I haven't managed to achieve to group elements by type, instead my key is a List<String>.Also my list should have a group by color as well.

3
  • 1
    you could put your current version code on SO...let us help to improve it Commented Jul 23, 2020 at 14:04
  • 3
    Well, what do you want to happen if a MasterObject contains multiple subobjects with different types and colors? Commented Jul 23, 2020 at 14:04
  • 1
    I would like my Map to have different entries grouped by first type and second color for the same date Commented Jul 23, 2020 at 14:34

2 Answers 2

5

I would use a custom object to keep flattened values:

public class CustomObject {
    private String date;
    private String type;
    private String color;
    private MasterObject masterObj;
    
    // constructor, getters, setters
}

Try this:

SubObject sub1 = new SubObject("typeA", "red");
SubObject sub2 = new SubObject("typeA", "blue");
SubObject sub3 = new SubObject("typeB", "green");
SubObject sub4 = new SubObject("typeB", "green");
SubObject sub5 = new SubObject("typeC", "red");
SubObject sub6 = new SubObject("typeC", "blue");

List<MasterObject> masterObjList = new ArrayList<>();

masterObjList.add(new MasterObject("01/01/2020", Arrays.asList(sub1, sub2, sub3)));
masterObjList.add(new MasterObject("02/01/2020", Arrays.asList(sub4, sub5, sub6)));

Map<String, Map<String, Map<String, MasterObject>>> result = masterObjList.stream().flatMap(
        o -> o.getSubObject().stream().map(s -> new CustomObject(o.getDate(), s.getType(), s.getColor(), o)))
        .collect(Collectors.groupingBy(CustomObject::getDate, Collectors.groupingBy(CustomObject::getType,
                Collectors.toMap(CustomObject::getColor, CustomObject::getMasterObj))));

result.entrySet().forEach(e -> {
    System.out.println(e.getKey());
    e.getValue().entrySet().forEach(e1 -> {
        System.out.println("\t" + e1.getKey());
        e1.getValue().entrySet().forEach(e2 -> {
            System.out.println("\t\t" + e2.getKey());
        });
    });
});

Output:

01/01/2020
    typeB
        green
    typeA
        red
        blue
02/01/2020
    typeC
        red
        blue
    typeB
        green
Sign up to request clarification or add additional context in comments.

3 Comments

It seems that you define a middle-switch class named CustomObject . Is there exists one-way method?
There is no benefit of using Stream API if all you do is calling forEach, which in fact, is available to Iterables. So e.getValue().entrySet().forEach(..) is sufficient.
I've realized that I use it this way most of the time. Good eye! Thanks, I greatly appreciate your help @NikolasCharalambidis
2

The solution by @Hülya is great, and scale better on complex data structure.

An alternative and more concise way to do it, potentially faster (if perf is important) due to avoiding the intermediate object, is to just look on the element and add in the map on the go:

public Map<String, Map<String, Map<String, MasterObject>>> group (List<MasterObject> objects) {

  Map<String, Map<String, Map<String, MasterObject>>> byDate = new HashMap<>();
  for (MasterObject m : objects) {
    Map<String, Map<String, MasterObject>> byType = byDate.computeIfAbsent(m.date, k -> new HashMap<>();); 
    for (SubObject sub : m.subObject) {          
      Map<String, Map<String, MasterObject>> byColor = byType.computeIfAbsent(sub.type, k -> new HashMap<>(););
      byColor.put(sub.color, m);
    }
  }
  return byDate;
}

After all a groupBy is just that, iterating over all elements and put them in the right bucket. And sometime the iterative approach is shorter/smaller and as readable as the functional one.

Just for fun, in C++, lambda are annoying to use (it improved in recent versionsà and the imperative approach is even more concise than in java:

map<string, map<string, map<string, MasterObject>>> group (const vector<MasterObject> &objects) {

  map<string, map<string, map<string, MasterObject>>> result;
  for (auto&& m : objects) {
    for (auto &&sub : m.subObject) {
      result[m.date][sub.type][sub.color] = m;
    }
  }
  return result;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.