Make a map of Department to List of employee from employee object containing list of departments

Viewed 2034

Question: Employee contains list of departments and we have a list of employees now how to obtain a map like Map<Department,List of Employees> from employee list.

The below code does the trick but I wanted to know how to use streams api effectively instead of for loop.

    Department a = new Department("a");
    Department b = new Department("b");
    Department c = new Department("c");

    Employee e1 = new Employee("e1", List.of(a, b));
    Employee e2 = new Employee("e2", List.of(c, b));
    Employee e3 = new Employee("e3", List.of(c, a));
    Employee e4 = new Employee("e4", List.of(a, b, c));

    List<Employee> employees = List.of(e1, e2, e3, e4);
    Set<Department> departments = employees.stream().flatMap(employee ->
            employee.getDepartments().stream()).collect(Collectors.toSet());

    for (Department d : departments) {
        for (Employee employee : employees) {
            if (employee.getDepartments().contains(d)) {
                if (!result.containsKey(d)) {
                    result.put(d, new ArrayList<Employee>());
                }
                result.get(d).add(employee);
            }
        }
    }
    return result;
3 Answers

Even without any stream-magic it's probably worth mentioning that you need only one loop-pair, over what you already have (the employees and their departments):

[...]
List<Employee> employees = List.of(e1, e2, e3, e4);
for (Employee employee : employees) {
    for (Department d : employee.getDepartments()) {
        if (!result.containsKey(d)) {
            result.put(d, new ArrayList<Employee>());
        }
        result.get(d).add(employee);
    }
}
return result;

then you can try some stream-flatmap-groupingby magic:

[...]
List<Employee> employees = List.of(e1, e2, e3, e4);
var result = employees.stream().flatMap(employee->employee.getDepartments().stream()
        .map(department->AbstractMap.SimpleImmutableEntry<>(department,employee))
    .collect(Collectors.groupingBy(pair->pair.getKey()));

here the drawback is that result is going to be a Map<Department,AbstractMap.SimpleImmutableEntry<Department,Employee>. (AbstractMap.SimpleImmutableEntry is a 2-element tuple, just it has a nice long name).

The snippet above may have typo-s inside, in reality I only ran your task using a Map<String,List<String>> as the employee-departments thing, as I didn't want to write the supplementary classes:

public static void main(String[] args) {
    var a="a";
    var b="b";
    var c="c";
    var empdep=new HashMap<String, List<String>>();
    empdep.put("e1", List.of(a, b));
    empdep.put("e2", List.of(c, b));
    empdep.put("e3", List.of(c, a));
    empdep.put("e4", List.of(a, b, c));
    System.out.println(empdep);
    
    var depemp=new HashMap<String, List<String>>();
    for(var employee:empdep.entrySet())
        for(var department:employee.getValue()) {
            if(!depemp.containsKey(department))
                depemp.put(department, new ArrayList<String>());
            depemp.get(department).add(employee.getKey());
        }
    System.out.println(depemp);
        
    System.out.println(
            empdep.entrySet().stream().flatMap(employee->employee.getValue().stream()
                    .map(department->new AbstractMap.SimpleImmutableEntry<>(department, employee.getKey())))
                    .collect(Collectors.groupingBy(pair->pair.getKey()))
            );
}

this code outputs

{e1=[a, b], e2=[c, b], e3=[c, a], e4=[a, b, c]}
{a=[e1, e3, e4], b=[e1, e2, e4], c=[e2, e3, e4]}
{a=[a=e1, a=e3, a=e4], b=[b=e1, b=e2, b=e4], c=[c=e2, c=e3, c=e4]}

where the first line is the input "list" (just it's a map here, but looping over its entrySet() is just the same thing as a list), the second line is the result of the for-loop pair, producing the desired map, and the third line is the result of the stream-magic, but with a list of department-employee pairs "inside" the departments.


And that was yesterday, and today is today. I learned a bit more about groupingBy(), and mapping(). This "line"

System.out.println(
    empdep.entrySet().stream().flatMap(employee->employee.getValue().stream()
        .map(department->new AbstractMap.SimpleImmutableEntry<>(department, employee.getKey())))
        .collect(Collectors.groupingBy(pair->pair.getKey(),Collectors.mapping(pair->pair.getValue(), Collectors.toList()))));

Produces the desired output with the previous String-String example,

{a=[e1, e3, e4], b=[e1, e2, e4], c=[e2, e3, e4]}

And then the full code, with Employee and Department classes:

public class Test {
    public static void main(String[] args) {
        Department a = new Department("a");
        Department b = new Department("b");
        Department c = new Department("c");

        Employee e1 = new Employee("e1", List.of(a, b));
        Employee e2 = new Employee("e2", List.of(c, b));
        Employee e3 = new Employee("e3", List.of(c, a));
        Employee e4 = new Employee("e4", List.of(a, b, c));

        List<Employee> employees = List.of(e1, e2, e3, e4);
        Map<Department,List<Employee>> result=employees.stream()
            .flatMap(employee->employee.getDepartments().stream()
                .map(department->new Pair(department,employee)))
            .collect(Collectors.groupingBy(pair->pair.d,
                                           Collectors.mapping(pair->pair.e,
                                                              Collectors.toList())));
        System.out.println(result);
    }
    
    static class Department{final String name;Department(String name){this.name=name;}public String toString(){return name;}}
    static class Employee{final String name;final List<Department> departments;Employee(String name,List<Department> departments){this.name=name;this.departments=departments;}List<Department> getDepartments(){return departments;}public String toString() {return name;}}
    // this is just a helper class instead of AbstractMap.whatever
    static class Pair{final Department d;final Employee e;Pair(Department d,Employee e){this.d=d;this.e=e;}}
}

This code produces the desired Map<Department,List<Employee>> result, and prints

{b=[e1, e2, e4], a=[e1, e3, e4], c=[e2, e3, e4]}

Also on IdeOne

You can do it as follows:

Map<Department, List<Employee>> map = 
    departments.stream()
            .collect(Collectors.toMap(
                        Function.identity(),
                        d -> employees.stream()
                                    .filter(e -> e.getDepartments().contains(d))
                                    .collect(Collectors.toList())
                    )
            );

ONLINE DEMO

Sorry, on mobile. Cannot add sample code at the moment

  1. Stream all employees
  2. Flat map to Pair (Apache) or Map Entry (JDK AbstractMap Simple immutable Entry)
  3. Collect to map. Key mapping is the department, value mapping. This is done by grouping by department, storing the employees as list (see related question: https://stackoverflow.com/a/49889657/1870799 )
Related