2015-07-04 8 views
7

Ich habe die Person Klasse:Fest mit Lambda-Ausdruck und Map

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

Und die App Klasse:

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 der App Klasse die PeoplePerSkill Methode müssen die Set von Menschen Namen zurückzukehren pro Fertigkeit. Es bedeutet, dass eine Fähigkeit von vielen Menschen gehört werden könnte.

ich mit den groupingBy(p->p...........,) steckte ich kann einfach nicht die String des Namen Fähigkeit bekommen, habe ich versucht, so viele Möglichkeiten, aber die Dinge Weg Fremdes :(.

By the way, mein derzeitiger Code gibt Map<Object, Set<String>>

Antwort

6

Sie können es über Flat-Mapping tun, obwohl es wahrscheinlich sieht nicht sehr schön:

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

Hier flatMap einen Strom von Paaren erzeugt (skill, person name), die col sind ähnlich wie bei Ihnen. Ich verwende die AbstractMap.SimpleEntry Klasse, um das Paar darzustellen, Sie können etwas anderes verwenden.

return StreamEx.of(this.people) 
     .mapToEntry(p -> p.getSkills().keySet(), Person::getName) 
     .flatMapKeys(Set::stream) 
     .grouping(toSet()); 

Intern fast das gleiche, es ist nur Zucker syntaktische:

kann meine StreamEx Bibliothek diese Aufgabe verwenden schönere gelöst werden.

Update: scheint, dass meine ursprüngliche Lösung war falsch: Es Karte zurück person_name -> [skills], aber wenn ich die OP richtig verstehe, will er Karte skill -> [person_names]. Die Antwort wurde bearbeitet.

1

Ich bin mir nicht sicher, ob Streams Ihr Leben hier leichter machen würden. IMO dieser Code ist viel einfacher zu lesen und zu reinigen.

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

Sie können auch "vereinfachen"

map.putIfAbsent(skill, new HashSet<>()); 
map.get(skill).add(person.getName()); 

mit

map.computeIfAbsent(skill, k -> new HashSet<>()).add(person.getName()); 
1

Wenn Sie externe Bibliotheken in Ihrem Code verwenden können, könnten Sie mit einem Multimap anstelle eines Map<String, Set<String>> berücksichtigen wollen . Leider eine Lösung mit einem Multimap mit wird mehr Textvorschlag verlangen, da sie nicht offiziell von der JDK unterstützt wird, aber es sollte auf eine „sauberere“ Lösung führen:

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

Beachten Sie, wie Ich musste die generischen Parameter an toMultimap() liefern. Java 8 hat viel bessere generische Inferenz, aber es does not infer chained method calls.

Sie müssen entweder die generischen Parameter explizit angeben oder eine lokale Variable Function<Map<String, Set<String>>, Multimap<String, String>> toMultimap deklarieren, damit der Compiler die Typparameter richtig erkennt.