2015-05-14 4 views
7

Ho il seguente problema: Alla luce di queste classi,Come elementi di gruppo di un elenco di elementi di un altro in Java 8

class Person { 
    private String zip; 
    ... 
    public String getZip(){ 
     return zip; 
    } 
} 

class Region { 
    private List<String> zipCodes; 
    ... 
    public List<String> getZipCodes() { 
     return zipCodes; 
    } 
} 

utilizzando l'API flusso Java 8, come faccio a ottenere un Map<Person, List<Region>> a seconda che lo Region contiene il codice postale di Person? In altre parole, come faccio a raggruppare le regioni in base alle persone i cui codici postali appartengono a tali regioni?

ho fatto in Java 7 alla vecchia maniera, ma ora ho migrare il codice per sfruttare le nuove funzionalità di Java 8.

Grazie,

Impeto

+0

Se una persona ha un unico codice postale, come può quella persona appartenere a più regioni? Un codice postale può appartenere a più di una regione? – Eran

+0

@Eran Sì, le regioni sono elenchi di codici postali con un nome. Possono sovrapporsi. – impeto

+0

Lasciami qualificare meglio. Un codice postale può appartenere a una città, a una contea oa uno stato o può appartenere a un gruppo personalizzato di codici postali in base alle esigenze aziendali. – impeto

risposta

5

ho il sospetto il modo più pulito per fare questo - Io non sono molto felici con le altre risposte postato - sarebbe

persons.stream().collect(Collectors.toMap(
    person -> person, 
    person -> regions.stream() 
     .filter(region -> region.getZipCodes().contains(person.getZip())) 
     .collect(Collectors.toList()))); 
+0

Questo non viene compilato per me. Inizialmente ho provato la stessa cosa, ma non riesce a '... contiene (person.getZip())' dicendo che non riesce a trovare il metodo 'getZip()'. Dice 'persona' c'è un parametro lambda. – MadConan

+1

Posso ottenere il messaggio di errore completo? Potrebbe essere necessario scrivere '(Person person) ->' all'inizio del lambda ... –

+0

Bene, ora non riesco a replicare il problema. Non sono sicuro di quale fosse il problema, ma dopo aver chiuso IntelliJ e avviato il backup, il problema di compilazione è scomparso. – MadConan

2

La risposta originale esegue una mappatura non necessaria con le tuple, quindi è possibile vedere la soluzione finale. Si potrebbe rimuovere il mapping, e semplicemente filtrare direttamente la lista regions:

//A Set<Region> is more appropriate, IMO 
.stream() 
.collect(toMap(p -> p, 
       p -> regions.stream() 
          .filter(r -> r.getZipCodes().contains(p.getZip())) 
          .collect(toSet()))); 


Se ho capito bene, si potrebbe fare qualcosa di simile:

import java.util.AbstractMap.SimpleEntry; 
import static java.util.stream.Collectors.toMap; 
import static java.util.stream.Collectors.toList; 

... 

List<Person> persons = ...; 
List<Region> regions = ...; 

Map<Person, List<Region>> map = 
    persons.stream() 
      .map(p -> new SimpleEntry<>(p, regions)) 
      .collect(toMap(SimpleEntry::getKey, 
          e -> e.getValue().stream() 
              .filter(r -> r.getZipCodes().contains(e.getKey().getZip())) 
              .collect(toList()))); 

Dal List<Person> si ottiene un Stream<Person>. Quindi mappare ogni istanza a una tupla <Person, List<Region>> che contiene tutte le regioni. Da lì, raccogli i dati in una mappa con il raccoglitore toMap e, per ogni persona, crei un elenco di Region contenente il codice di avviamento postale di quella persona.

Ad esempio, dato l'ingresso:

List<Person> persons = Arrays.asList(new Person("A"), new Person("B"), new Person("C")); 

List<Region> regions = 
    Arrays.asList(new Region(Arrays.asList("A", "B")), new Region(Arrays.asList("A"))); 

Produce:

Person{zip='A'} => [Region{zipCodes=[A, B]}, Region{zipCodes=[A]}] 
Person{zip='B'} => [Region{zipCodes=[A, B]}] 
Person{zip='C'} => [] 

Inoltre immagino la zipCodes per ogni Region potrebbe essere un Set.

+1

Perché preoccuparsi di andare via SimpleEntry? –

+1

@LouisWasserman Sì, questo è quello che ho pensato mentre dormivo e mi sono svegliato per modificarlo. Modifica ... Ah, vedo che hai già risposto ... –

+0

In teoria ha senso essere un Set, ma praticamente ci sono dei vincoli nel database che assicurano che non ci siano codici postali duplicati in una regione. Progetto sempre i database con i vincoli più stretti e quindi posso permettermi di ridurre un po 'di gioco a livello di applicazione :) – impeto

1

Ho non eseguito alcun test di questo codice, ma esso compila quindi deve essere corretto (: eyeroll :).

public Map<Person,List<Region>> mapPeopleToRegion(List<Person> people, List<Region> regions){ 
    final Map<Person,List<Region>> personToRegion = new HashMap<>(); 
    people.forEach(person -> 
      personToRegion.put(
       person,regions.stream().filter(
         region -> region.getZipCodes().contains(person.getZip())) 
         .collect(Collectors.toList()))); 
    return personToRegion; 
} 
0

E 'ancora piuttosto brutto, e penso che sarebbe migliorata cambiando come si modella le cose un po ', ma sono riuscito solo a venire con il seguente finora:

public static void main(String[] args) { 
    Person[] people = {new Person("00001"), new Person("00002"), new Person("00005")}; 
    Region[] regions = { 
      new Region("Region 1", Arrays.asList("00001", "00002", "00003")), 
      new Region("Region 2", Arrays.asList("00002", "00003", "00004")), 
      new Region("Region 3", Arrays.asList("00001", "00002", "00005")) 
    }; 

    Map<Person, List<Region>> result = Stream.of(regions) 
      .flatMap(region -> region.getZipCodes().stream() 
        .map(zip -> new SimpleEntry<>(zip, region))) 
      .flatMap(entry -> Stream.of(people) 
        .filter(person -> person.getZip().equals(entry.getKey())) 
        .map(person -> new SimpleEntry<>(person, entry.getValue()))) 
      .collect(Collectors.groupingBy(Entry::getKey, Collectors.mapping(Entry::getValue, Collectors.toList()))); 

    result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue())); 

    //  Output: 
    //  [Person: 0]: {[name: Region 1, name: Region 3]} 
    //  [Person: 1]: {[name: Region 1, name: Region 2, name: Region 3]} 
    //  [Person: 2]: {[name: Region 3]} 
} 

Avere una classe ZipCode che conteneva la mappatura e potrebbe essere digitato sulla sarebbe ma ke cose più pulite:

public static void main(String[] args) { 
     Region r1 = new Region("Region 1"); 
     Region r2 = new Region("Region 2"); 
     Region r3 = new Region("Region 3"); 

     ZipCode zipCode1 = new ZipCode("00001", Arrays.asList(r1, r3)); 
     ZipCode zipCode2 = new ZipCode("00002", Arrays.asList(r1, r2, r3)); 
     ZipCode zipCode3 = new ZipCode("00003", Arrays.asList()); 
     ZipCode zipCode4 = new ZipCode("00004", Arrays.asList()); 
     ZipCode zipCode5 = new ZipCode("00005", Arrays.asList(r3)); 

     Person[] people = { 
       new Person(zipCode1), 
       new Person(zipCode2), 
       new Person(zipCode5) 
     }; 

     Map<Person, List<Region>> result = Stream.of(people) 
      .collect(Collectors.toMap(person -> person, 
        person -> person.getZip().getRegions())); 

     result.entrySet().forEach(entry -> System.out.printf("[%s]: {%s}\n", entry.getKey(), entry.getValue())); 

//  Output: 
//  [Person: 0]: {[name: Region 1, name: Region 3]} 
//  [Person: 1]: {[name: Region 1, name: Region 2, name: Region 3]} 
//  [Person: 2]: {[name: Region 3]} 
} 
0

Alcune delle altre risposte contengono codice che esegue molte ricerche lineari attraverso elenchi. Penso che la soluzione Java 8 Stream non dovrebbe essere molto più lenta della variante classica. Quindi ecco una soluzione che sfrutta gli Stream senza sacrificare molte prestazioni.

List<Person> people = ... 
List<Region> regions = ... 

Map<String, List<Region>> zipToRegions = 
    regions.stream().collect(
     () -> new HashMap<>(), 
     (map, region) -> { 
      for(String zipCode: region.getZipCodes()) { 
       List<Region> list = map.get(zipCode); 
       if(list == null) list = new ArrayList<>(); 
       list.add(region); 
       map.put(zipCode, list); 
      } 
     }, 
     (m1, m2) -> m1.putAll(m2) 
    ); 
Map<Person, List<Region>> personToRegions = 
    people.stream().collect(
    Collectors.toMap(person -> person, 
        person -> zipToRegions.get(person.getZip())) 
);