2015-07-04 13 views
7

ho la classe Person:Bloccato con espressione lambda e mappa

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

E la classe App:

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

Nella classe App il metodo PeoplePerSkill bisogno di restituire il Set di nomi di persone per abilità. Significa che un'abilità potrebbe essere di proprietà di molte persone.

ho bloccato con il groupingBy(p->p...........,) non riesco proprio a ottenere il String del nome di abilità, ho provato tanti modi, ma le cose si fanno strada straniero :(.

Tra l'altro, Attualmente il mio codice restituisce Map<Object, Set<String>>

risposta

6

si può fare tramite flat-mapping, anche se probabilmente non sembra molto bello:

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

Qui flatMap crea un flusso di coppie (skill, person name), che sono col selezionato in modo abbastanza simile al tuo. Sto usando la classe AbstractMap.SimpleEntry per rappresentare la coppia, puoi usare qualcos'altro.

Utilizzando la mia libreria StreamEx questo compito può essere risolto più bella:

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

Internamente è quasi la stessa, lo zucchero sintattico.

Aggiornamento: sembra che la mia soluzione originale era sbagliato: è tornato mappa person_name -> [skills], ma se ho capito bene il PO, vuole mappa skill -> [person_names]. La risposta è stata modificata.

1

Non sono sicuro che i flussi possano semplificarti la vita qui. IMO questo codice è molto più facile da leggere e più pulito.

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

È inoltre possibile "semplificare"

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

con

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

Se è possibile utilizzare librerie esterne nel codice, si potrebbe voler considerare l'utilizzo di un Multimap invece di un Map<String, Set<String>> . Purtroppo, una soluzione che utilizza un Multimap sta andando a richiedere più boilerplate dal momento che non è ufficialmente supportato dal JDK, ma dovrebbe portare a una soluzione più "pulito":

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

Notate come Ho dovuto fornire i parametri generici a toMultimap(). Java 8 ha inferenza generica molto migliore, ma è does not infer chained method calls.

Sarà necessario fornire esplicitamente i parametri generici o dichiarare una variabile locale Function<Map<String, Set<String>>, Multimap<String, String>> toMultimap in modo che il compilatore deduca correttamente i parametri di tipo.