sakura
sakura

Reputation: 422

Java 8 group by individual elements of list

class Person {
    String personName;
    List<Skill> skills;
}

class Skill {
    String skillName;
}

How to obtain a map of

  1. skillName and personName
  2. skillName and person Object

E.g. John has skills painting, kungfu. James has cooking, karate, kungfu, Chris has painting, hiking

Output

painting -> John, Chris
kungfu -> John, James
cooking -> James
karate -> James
hiking -> Chris

What I have tried:

list.stream().collect(Collectors.groupingBy(e -> e.skills.stream(), Collectors.toSet()))

I know this does not work, because you cannot group by e.skills.stream().

list.stream().flatMap(e -> e.skills.stream()).collect(Collectors.groupingBy(e -> e.skillName, Collectors.toSet()));

It does group by skillName (but only Skill objects), however reference to Person is lost due to flatMap.

Upvotes: 1

Views: 207

Answers (2)

Ryuzaki L
Ryuzaki L

Reputation: 40078

You can create Map.Entry object for every Skill_name->Person_name combination, and then use Collectors.groupingBy for grouping the person names with skills

Map<String, List<String>> groupingBySkill = persons.stream()
            .flatMap(p -> p.getSkills().stream().map(Skill::getSkillName)
                    .map(sk -> new AbstractMap.SimpleEntry<String, String>(sk, p.getPersonName())))
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                    Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

Upvotes: 1

Jacob G.
Jacob G.

Reputation: 29730

I think this would be easier to accomplish if you first converted your List<Person> into a Stream<PersonNameSkill>, where PersonNameSkill represents a nominal tuple containing the Person's name and a single Skill that is attributed to them.

In Java 14, you can take advantage of record to create this nominal tuple:

record PersonNameSkill(String personName, String skillName) {}

Now that this exists, you can write a solution (Java 8+):

list.stream().flatMap(person -> person.skills.stream()
        .map(skill -> new PersonNameSkill(person.personName, skill.skillName)))
    .collect(Collectors.groupingBy(PersonNameSkill::skillName,
        Collectors.mapping(PersonNameSkill::personName, Collectors.toSet())));

If you're using an older version of Java, you can create PersonNameSkill with:

public class PersonNameSkill {
    private final String personName;
    private final String skillName;

    // Constructor, getters, equals, hashCode, etc.
}

Upvotes: 2

Related Questions