2016-05-19 3 views
1

Wir habenWas ist eine bessere Möglichkeit, Java-Liste in Map of Maps zu brechen?

List<persons> persons; 

und wir brauchen

Map<age,Map<income,Person>> results 

so, wie ich es jetzt tue:

 ages.stream().forEach(a -> { 
     Map<Integer,Person> tmpMap = new HashMap<>(); 
     incomes.stream().forEach(i -> { 
      Person p = persons.stream().filter(
      u -> u.getAge() == a.getAge() && 
      u.getIncome() == i.getIncome()) 
      .findAny().orElse(null); 
      tmpMap.put(i.getIncome(), p); 
     }); 
     returns.put(a.getAge(),tmpMap); 
    }); 

es wie sollte es scheint, dies zu tun einen besseren Weg geben.

+0

Haben Sie Guava Bibliothek in Betracht gezogen? Ich glaube, es hat die Umsetzung für das, was Sie brauchen. – YaRiK

+1

Nicht sicher, wie Ihre Karte und Klassen aussehen, aber wissen Sie, dass Sie möglicherweise ein Problem haben, wenn paar Personen Alter oder Einkommen teilen – user902383

+1

Seitliche Anmerkung: Java Klassennamen beginnen mit Großbuchstaben per Konvention. Das Ignorieren dieser Konvention erschwert es anderen Java-Programmierern, Ihren Code zu lesen. also überlege, das zu beheben. – GhostCat

Antwort

3

Das sieht aus wie es funktioniert.

List<Person> people = Arrays.asList(
     new Person("One", 21, 100), 
     new Person("Two", 21, 75), 
     new Person("Three", 42, 100), 
     new Person("Four", 42, 120), 
     new Person("Five", 9, 100) 
); 
Map<Integer, Map<Integer, Person>> map = people.stream() 
     // Gather all ages into a Map<Age,List<Person>> 
     .collect(Collectors.groupingBy(Person::getAge)) 
     // Walk that transient Map. 
     .entrySet().stream() 
     .collect(Collectors.toMap(
       // Key is the age. 
       Map.Entry::getKey, 
       // Value is a Map<income,person> 
       e -> e.getValue() 
       // Roll each of the same age into a Map<Income,Person> 
       .stream().collect(
         Collectors.toMap(
           // Key is income. 
           Person::getIncome, 
           // Value is the Person. 
           Function.identity() 
         )))); 

ich rolle Ihre Liste in eine Map<Age,List<Person>> ein groupingBy und anschließend streamen es entrySet ist und sammeln, die in die endgültige Form.

Dies wird fehlschlagen, wenn zwei Personen im gleichen Alter das gleiche Einkommen haben, weil das die innere Karte verletzen wird. Verwenden Sie Alexander's suggestion, wenn Sie mit der natürlichen Verbesserung der Generierung eines Map<Integer, Map<Integer, List<Person>>> zufrieden sind.

Added

@Holger hat in einem Kommentar darauf hingewiesen, dass dies in eine viel einfacher und eleganter Weise erfolgen. Bitte verwenden Sie dieses Formular stattdessen/

Map<Integer, Map<Integer, Person>> map2 = people.stream() 
     .collect(
       Collectors.groupingBy(
         Person::getAge, 
         Collectors.toMap(Person::getIncome, Function.identity()))); 

FYI - Hier ist die Personenklasse, die ich verwendet habe.Beachten Sie, dass die equals und hashcode implementiert sind.

class Person { 
    private final String name; 
    private final int age; 
    private final int income; 


    public Person(String name, int age, int income) { 
     this.name = name; 
     this.age = age; 
     this.income = income; 
    } 

    public String getName() { 
     return name; 
    } 

    public int getAge() { 
     return age; 
    } 

    public int getIncome() { 
     return income; 
    } 

    @Override 
    public String toString() { 
     return "Person{" + "name=" + name + ", age=" + age + ", income=" + income + '}'; 
    } 

    @Override 
    public int hashCode() { 
     int hash = 7; 
     hash = 59 * hash + Objects.hashCode(this.name); 
     hash = 59 * hash + this.age; 
     hash = 59 * hash + this.income; 
     return hash; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) { 
      return true; 
     } 
     if (obj == null) { 
      return false; 
     } 
     if (getClass() != obj.getClass()) { 
      return false; 
     } 
     final Person other = (Person) obj; 
     if (this.age != other.age) { 
      return false; 
     } 
     if (this.income != other.income) { 
      return false; 
     } 
     if (!Objects.equals(this.name, other.name)) { 
      return false; 
     } 
     return true; 
    } 
} 
+0

@NicolasFilotto - Danke - gute Anrufe beide. – OldCurmudgeon

+0

ein links e -> e.getValue() –

+0

@NicolasFilotto - Versucht es. Hat nicht funktioniert, wahrscheinlich, weil ich dann die Einträge streame. – OldCurmudgeon

1

Warum nicht (ich Ihre Art annehmen, ist Person, nicht Personen, verwendet man beide)

for (person p : persons) 
    { 
     if (!results.containsKey(p.getAge()) 
      results.put(p.getAge(), new HashMap<income,persons>()); 
     results.get(p.getAge()).put(p.getIncome(), p); 
    } 
+0

Menschen sind so geneigt, Streams zu verwenden, dass sie vergessen, dass dasselbe auf eine andere, viel sauberere Art und Weise erreicht werden kann. Ein großes Dankeschön an Sie, Sir. –

+0

Sogar das funktioniert besser mit Java 8: 'for (Person p: persons) results.computeIfAbsent (p.getAge(), Schlüssel -> new HashMap <>()). Put (p.getIncome(), p);' – Holger

+0

@Holger: Wir sind immer noch auf Java7, also bin ich nicht so vertraut mit der neuen Syntax ... –

2

Sie einen Blick auf Collectors.groupingBy nehmen sollte():

 List<Person> persons = new ArrayList<>(); 
     Map<Integer, Map<Integer, List<Person>>> map = persons.stream().collect(Collectors.groupingBy(person -> person.getAge(),Collectors.groupingBy(person -> person.getIncome()))); 

Diese sollte dein Ding tun.

+0

Dies erstellt eine 'Map >>'. OP wollte eine 'Map >'. – OldCurmudgeon

3

Dies sollte den Trick

List<person> persons = Arrays.asList(
    new person(22, 1000), 
    new person(25, 1500), 
    new person(22, 2500), 
    new person(32, 5000) 
); 
Map<Integer, Map<Integer, List<person>>> map = persons.stream().collect(
    groupingBy(person::getAge, groupingBy(person::getIncome)) 
); 
System.out.println(map); 

Ausgang tun:

{32={5000=[person{age=32, income=5000}]}, 22={2500=[person{age=22, income=2500}], 1000=[person{age=22, income=1000}]}, 25={1500=[person{age=25, income=1500}]}} 

NB: Das Ergebnis ist nicht genau das, was Sie erwarten, wie Sie einen Map<Integer, Map<Integer, List<person>>> statt Map<Integer, Map<Integer, person>> aber ich bekommen gehe davon aus, dass deine erste Frage nicht richtig ist, denn wenn du zwei Personen mit gleichem Alter und gleichem Einkommen hast, hast du nur eine Person auf deiner Karte anstelle von zwei

+1

Bitte geben Sie einen Kommentar, wenn Sie unten abstimmen, ein großer Junge sein –

+0

@OldCurmudgeon Ich verstehe nicht, wie jemand mit Ihrem Ruf immer noch nicht weiß, dass Sie einen Kommentar hinterlassen sollen, wenn Sie unten abstimmen –

+0

Um [Vergeltung zu vermeiden] (http://meta.stackoverflow.com/a/255440/823393). – OldCurmudgeon

0

I zweite YaRiK; ein GuavaTable<Integer, Integer, Set<Person>> würde hier gut funktionieren. Sie sollten eine Set<Person> verwenden, um Kollisionen zu vermeiden, wie Benutzer902383 suggeriert. Die Stream-API ist nicht immer das richtige Tool, und das sieht für mich so aus, als ob eine traditionelle iterative Schleife much easier to read wäre.

Versuchen Sie folgendes:

Table<Integer, Integer, Set<Person>> table = HashBasedTable.create(); 
for (Person p : persons) { 
    Set<Person> s = table.get(p.getAge(), p.getIncome()); 
    if (s == null) { 
    s = new HashSet<>(); 
    table.put(p.getAge(), p.getIncome(), s); 
    } 
    s.add(p); 
} 
0

Sie können auch Ihre eigenen Kollektor für die Erreichung dieses umzusetzen. Mit Hilfe von Guava können Sie es in einer Zeile tun:

Map<Integer, Map<Integer, Person>> result = persons.stream().collect(HashMap::new, (store, person) -> store.put(person.getAge(), ImmutableMap.of(person.getIncome(), person)), HashMap::putAll); 
Verwandte Themen