I have the Person
class:
import java.util.*;
public class Person {
private String name;
Map<String,Integer> Skills=new HashMap<>(); // skill name(String) and level(int)
public String getName(){
return this.name;
}
public Map<String,Integer> getSkills(){
return this.Skills;
}
}
And the App
class:
import java.util.*;
import java.util.Map.Entry;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
public class App {
private List<Person> people=new ArrayList<>(); // the people in the company
public Map<String,Set<String>> PeoplePerSkill(){
return this.people.stream().collect(groupingBy(p-> p.getSkills().keySet() //<-get
//^problem here
,mapping(Person::getName,toSet())));
}
}
In the App
class the PeoplePerSkill
method need to return the Set
of people names per skill. It means a skill could be owned by many people.
I stuck with the groupingBy(p->p..........., )
I just can't get the String
of skill's name, I tried so many ways but things get way stranger :(.
By the way, currently my code returns Map<Object, Set<String>>
You can do it via flat-mapping, though it probably doesn't look very beautiful:
public Map<String,Set<String>> PeoplePerSkill(){
return this.people.stream()
.<Entry<String, String>>flatMap(p ->
p.getSkills().keySet()
.stream()
.map(s -> new AbstractMap.SimpleEntry<>(s, p.getName())))
.collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toSet())));
}
Here flatMap
creates a stream of pairs (skill, person name)
, which are collected in the manner quite similar to yours. I'm using the AbstractMap.SimpleEntry
class to represent the pair, you may use something else.
Using my StreamEx library this task can be solved prettier:
return StreamEx.of(this.people)
.mapToEntry(p -> p.getSkills().keySet(), Person::getName)
.flatMapKeys(Set::stream)
.grouping(toSet());
Internally it's almost the same, just syntactic sugar.
Update: seems that my original solution was wrong: it returned map person_name -> [skills]
, but if I understand the OP correctly, he wants map skill -> [person_names]
. The answer edited.
I am not sure if streams would make your life easier here. IMO this code is much easier to read and cleaner.
public Map<String, Set<String>> peoplePerSkill() {
Map<String, Set<String>> map = new HashMap<>();
for (Person person : people) {
for (String skill : person.getSkills().keySet()) {
map.putIfAbsent(skill, new HashSet<>());
map.get(skill).add(person.getName());
}
}
return map;
}
You can also "simplify"
map.putIfAbsent(skill, new HashSet<>());
map.get(skill).add(person.getName());
with
map.computeIfAbsent(skill, k -> new HashSet<>()).add(person.getName());
If you can use external libraries in your code, you might want to consider using a Multimap
instead of a Map<String, Set<String>>
. Unfortunately, a solution using a Multimap
is going to require more boilerplate since it isn't officially supported by the JDK, but it should lead to a "cleaner" solution:
public static void main(String[] args) {
Person larry = new Person("larry");
larry.getSkills().put("programming", 0);
larry.getSkills().put("cooking", 0);
Person nishka = new Person("nishka");
nishka.getSkills().put("programming", 0);
nishka.getSkills().put("cooking", 0);
Person mitul = new Person("mitul");
mitul.getSkills().put("running", 0);
mitul.getSkills().put("cooking", 0);
Person rebecca = new Person("rebecca");
rebecca.getSkills().put("running", 0);
rebecca.getSkills().put("programming", 0);
List<Person> people = Arrays.asList(larry, nishka, mitul, rebecca);
Multimap<String, String> peopleBySkills = people.stream().collect(
collectingAndThen(toMap(Person::getName, p -> p.getSkills().keySet()),
CollectingMultimap.<String, String, Set<String>> toMultimap()
.andThen(invert())));
System.out.println(peopleBySkills);
}
private static <K, V, I extends Iterable<V>> Function<Map<K, I>, Multimap<K, V>> toMultimap() {
return m -> {
Multimap<K, V> map = ArrayListMultimap.create();
m.entrySet().forEach(e -> map.putAll(e.getKey(), e.getValue()));
return map;
};
}
private static <K, V> Function<Multimap<K, V>, Multimap<V, K>> invert() {
return m -> {
return Multimaps.invertFrom(m, ArrayListMultimap.create());
};
}
{running=[mitul, rebecca], cooking=[nishka, larry, mitul], programming=[nishka, larry, rebecca]}
Notice how I had to supply the generic parameters to toMultimap()
. Java 8 has much better generic inference, but it does not infer chained method calls.
You will either need to explicitly supply the generic parameters or declare a local variable Function<Map<String, Set<String>>, Multimap<String, String>> toMultimap
in order for the compiler to correctly infer the type parameters.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With