2017-09-28 5 views
0

Ich muss alle möglichen Kombinationen einer Art von Key erstellen, die aus X (in meinem Fall 8), gleich wichtige Elemente besteht. So kam ich so mit dem Code oben:Erstellen Sie alle möglichen Kombinationen von Elementen

final LinkedList<Key> keys = new LinkedList(); 

firstElementCreator.getApplicableElements() // All creators return a Set of elements 
      .forEach(first -> secondElementCreator.getApplicableElements() 
      .forEach(second -> thirdElementCreator.getApplicableElements() 
      // ... more creators 
      .forEach(X -> keys.add(new Key(first, second, third, ..., X))))))))); 

return keys; 

und es funktioniert, aber es gibt X verschachtelten forEach und ich habe das Gefühl, dass ich verpasste leichter/besser/elegantere Lösung. Irgendwelche Vorschläge? Vielen Dank im Voraus!

+0

Wenn Sie eine beliebige Anzahl von Schleifen benötigen, ist die Rekursion fast immer die Antwort. –

Antwort

1

Ist es kartesisches Produkt? Viele Bibliotheken bieten die API, zum Beispiel: Sets und Lists in Guava:

List<ApplicableElements> elementsList = Lists.newArrayList(firstElementCreator, secondElementCreator...).stream() 
     .map(c -> c.getApplicableElements()).collect(toList()); 

List<Key> keys = Lists.cartesianProduct(elementsList).stream() 
     .map(l -> new Key(l.get(0), l.get(1), l.get(2), l.get(3), l.get(4), l.get(5), l.get(6), l.get(7))).collect(toList()); 
+0

Dies ist eine Antwort, nach der ich gesucht habe. Dennoch hat diese Lösung einen Nachteil: Jedes Element hat einen anderen Typ (jetzt sehe ich, dass ich das nicht erwähnt habe), daher wäre Typengiessen notwendig. Vielleicht ist es möglich, eine gemeinsame Schnittstelle zu erstellen, ich werde darüber nachdenken. Vielen Dank. – szymonszymon

1

Da die Anzahl der Eingangssätze festgelegt ist (es hat die Anzahl der Argumente im Key-Konstruktor entsprechen), Ihre Lösung ist eigentlich nicht Schlecht.

Es ist effizienter und einfacher, ohne dass die Lambda-Ausdrücke zu lesen, obwohl, wie:

for (Element first : firstElementCreator.getApplicableElements()) { 
    for (Element second : secondElementCreator.getApplicableElements()) { 
     for (Element third : thirdElementCreator.getApplicableElements()) { 
      keys.add(new Key(first, second, third)); 
     } 
    } 
} 
+0

Sie haben Recht, Streams sind nicht immer die beste Lösung, das sieht definitiv besser aus, danke. – szymonszymon

1

Die kanonische Lösung ist flatMap zu verwenden. Der schwierige Teil besteht jedoch darin, das Objekt Key aus den mehreren Eingangspegeln zu erstellen.

Der Straight-Forward-Ansatz ist die Auswertung in der innersten Funktion zu tun, wo jeder Wert in

Umfang ist
final List<Key> keys = firstElementCreator.getApplicableElements().stream() 
    .flatMap(first -> secondElementCreator.getApplicableElements().stream() 
    .flatMap(second -> thirdElementCreator.getApplicableElements().stream() 
     // ... more creators 
     .map(X -> new Key(first, second, third, ..., X)))) 
    .collect(Collectors.toList()); 

aber das wird bald unpraktisch mit tiefer Verschachtelung

Eine Lösung ohne tiefe Verschachtelung erfordert Elemente, um Zwischenverbindungswerte zu halten. Z.B. wenn wir als Key

class Key { 
    String[] data; 
    Key(String... arg) { 
     data=arg; 
    } 
    public Key add(String next) { 
     int pos = data.length; 
     String[] newData=Arrays.copyOf(data, pos+1); 
     newData[pos]=next; 
     return new Key(newData); 
    } 

    @Override 
    public String toString() { 
     return "Key("+Arrays.toString(data)+')'; 
    } 
} 

(unter der Annahme, String als Elementtyp) definieren, können wir

final List<Key> keys = 
    firstElementCreator.getApplicableElements().stream().map(Key::new) 
     .flatMap(e -> secondElementCreator.getApplicableElements().stream().map(e::add)) 
     .flatMap(e -> thirdElementCreator.getApplicableElements().stream().map(e::add)) 
     // ... more creators 
     .collect(Collectors.toList()); 

Hinweis, dass diese flatMap Schritte werden nun auf der gleichen Ebene verwenden, das heißt nicht mehr verschachtelt. Auch sind alle diese Schritte identisch, unterscheiden sich nur in dem tatsächlichen Erzeuger, was zu der allgemeinen Lösung führt, die eine beliebige Anzahl von Instanzen unterstützt.

List<Key> keys = Stream.of(firstElementCreator, secondElementCreator, thirdElementCreator 
          /* , and, some, more, if you like */) 
    .map(creator -> (Function<Key,Stream<Key>>) 
        key -> creator.getApplicableElements().stream().map(key::add)) 
    .reduce(Stream::of, (f1,f2) -> key -> f1.apply(key).flatMap(f2)) 
    .apply(new Key()) 
    .collect(Collectors.toList()); 

hier jeder creator Zuordnung zu der identischen Strom produzierenden Funktion der bisherigen Lösung ist, dann sind alle mit einer einzigen Funktion reduzieren jede Funktion mit einem flatMap Schritt zum nächsten der Kombination und schließlich die resultierenden Funktion wird ausgeführt, um einen Stream zu erhalten, der dann zu einer List gesammelt wird.

Verwandte Themen