2

I have a List<Employee> e which I want to convert into Map<String, Map<String,Emp>> where the outer string should be "Name" and the inner string should be "Domain".

       Name Id Domain
e(0) - Emp1, 1, Insurance
e(1) - Emp1, 2, Sales
e(2) - Emp2, 3, Sales
e(3) - Emp4, 4, Marketing

I am tried the following-

e.stream().collect(Collectors.groupingBy(
                                   Employee::getName,
                                   toMap(Employee::getDomain,Emp)));

So the expected output map should look like

<Emp1>
     <Insurance, e(0)>
     <Sales, e(1)>
<Emp2>
     <Sales, e(2)>
<Emp4>
     <Marketing, e(3)>

But I get only unique values, actual output-

<Emp1>
     <Insurance, e(0)>
<Emp2>
     <Sales, e(2)>
<Emp4>
     <Marketing, e(3)>

Can someone tell the best way to do it?

2 Answers 2

1

What you are mostly looking for is nested grouping such as :

Map<String, Map<String, List<Employee>>> groupedMap = employees.stream()
        .collect(Collectors.groupingBy(Employee::getName,
                Collectors.groupingBy(Employee::getDomain, Collectors.toList())));

Note - The values are List<Employee> which are the employees grouped by name and then by domain. (Both same clubbed into a List.)


If you were to stricly adhere to getting a single employee corresponding to the specified grouping, the code pretty much works for me with a small modification:

Map<String, Map<String, Employee>> groupedReducedMap = employees.stream()
        .collect(Collectors.groupingBy(Employee::getName,
                Collectors.toMap(Employee::getDomain,
                        Function.identity(), // value as the employee instance
                        (a, b) -> a))); // choose first instance for similar 'domain'
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, for the required data structure the second one works
0

Since the output should be Map<String, Map<String,Employee>> and not Map<String, Map<String,List<Employee>>> (i.e. based on your requested output, there cannot be two Employees having the same name and the same domain), you can chain two groupingBy and then use reducing to ensure that each inner group will have a single Employee instead of a List<Employee>:

Map<String, Map<String,Optional<Employee>>> output =
    e.stream()
     .collect(Collectors.groupingBy(Employee::getName,
                                    Collectors.groupingBy(Employee::getDomain,
                                                          Collectors.reducing((x1,x2)->x2))));

The problem with this version of reducing is that it returns an Optional<Employee> instead of Employee, even though we know the Optional will never be empty.

We get can around that with:

  Map<String, Map<String,Employee>> output =
      e.stream()
       .collect(Collectors.groupingBy(Employee::getName,
                                      Collectors.groupingBy(Employee::getDomain,
                                                            Collectors.reducing(e.get(0),
                                                                                (x1,x2)->x2))));

Now, we use a variant of reducing having an identity value, to which we pass an arbitrary Employee instance (it doesn't matter which, since it will always be replaced by the correct instance).

1 Comment

IIRC, sometime back Holger pointed this out, that toMap is preferable to groupingBy + reducing. Here - in this Q&A

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.