Stuck with lambda expression and Map

六月ゝ 毕业季﹏ 提交于 2020-12-05 05:30:06

问题


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>>


回答1:


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.




回答2:


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());



回答3:


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.



来源:https://stackoverflow.com/questions/31223036/stuck-with-lambda-expression-and-map

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!