Report on a multimap by producing a new map of each key mapped to the count of elements in its collection value

Tags: , , , ,



Imagine a multimap tracking persons, each of whom has a list of assigned tasks.

Map< Person , List< Task > >  personToTasks = 
    Map.of(
        new Person( "Alice" ) , List.of( new Task( "a1" ), new Task( "a2") ) , 
        new Person( "Bob" )   , List.of( new Task( "b1" ) ) , 
        new Person( "Carol" ) , List.of( new Task( "c1" ), new Task( "c2"), new Task( "c3") ) 
    )
;

How can I use streams to get a new map, mapping each Person to an Integer with the count of items found in their list of assigned tasks?

How to get a result equivalent to this hard-coded map:

Map< Person , Integer >  personToTaskCount = 
    Map.of(
        new Person( "Alice" ) , 2 , 
        new Person( "Bob" )   , 1 , 
        new Person( "Carol" ) , 3 
    )
;

I have been trying permutations of:

Map < Person, Integer > personToTaskCount = 
        personToTasks.keySet().stream().collect
        (
            Collectors.mapping
            (
                Map.Entry :: getKey ,
                ???
            )
        )
;

Answer

You are on the right track, here’s a possible solution:

Map<Person, Integer> personToTaskCount = personToTasks
    .entrySet()
    .stream()
    .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().size()));

The Map#entrySet method returns a Set of the elements in a map, a set of Map.Entry objects. A Map.Entry holds the key-value pairing of each entry in the map. We can ask that object for the key and for the value.

In your example case, asking for the value of each Map.Entry gets us a List< Task >. We can then interrogate that list for its size, the number of elements it contains. This size number is the value we want for your new map, Map < Person, Integer >.

Here is an expanded version of the code above, to make more obvious these Map.Entry objects in action.

Map < Person, List < Task > > personToTasks =
        Map.of(
                new Person( "Alice" ) , List.of( new Task( "a1" ) , new Task( "a2" ) ) ,
                new Person( "Bob" ) , List.of( new Task( "b1" ) ) ,
                new Person( "Carol" ) , List.of( new Task( "c1" ) , new Task( "c2" ) , new Task( "c3" ) )
        );


Map < Person, Integer > personToTaskCount =
        personToTasks
                .entrySet()
                .stream()
                .collect(
                        Collectors.toMap(
                                ( Map.Entry < Person, List < Task > > e ) -> { return e.getKey(); } ,
                                ( Map.Entry < Person, List < Task > > e ) -> { return e.getValue().size(); }
                        )
                );

Dump results to console.

System.out.println( "personToTasks = " + personToTasks );
System.out.println( "personToTaskCount = " + personToTaskCount );

When run.

personToTasks = {Person[name=Alice]=[Task[title=a1], Task[title=a2]], Person[name=Carol]=[Task[title=c1], Task[title=c2], Task[title=c3]], Person[name=Bob]=[Task[title=b1]]}

personToTaskCount = {Person[name=Bob]=1, Person[name=Alice]=2, Person[name=Carol]=3}



Source: stackoverflow