2017-09-28 4 views
0

J'ai besoin de créer toutes les combinaisons possibles d'une sorte de clé, qui est composée de X (dans mon cas, 8), des éléments tout aussi importants. Alors je suis venu avec le code comme ceci:Créer toutes les combinaisons possibles d'éléments

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; 

et cela fonctionne, mais il y a X imbriqué forEach et j'ai le sentiment que je manque à une solution plus facile/mieux/plus élégant. Aucune suggestion? Merci d'avance!

+0

Lorsque vous avez besoin d'un nombre arbitraire de boucles, la récursivité est presque toujours la réponse. –

Répondre

1

Est-ce un produit cartésien? De nombreuses bibliothèques offrent l'API, par exemple: Sets et Lists à Goyave:

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

Ceci est une réponse que je cherchais. Pourtant, cette solution a un inconvénient: chaque élément de mon élément a un type différent (maintenant je vois que je n'ai pas mentionné cela), donc la conversion de type serait nécessaire. Peut-être qu'il est possible de créer une interface commune, je vais y réfléchir. Je vous remercie. – szymonszymon

1

Étant donné que le nombre d'entrées ensembles est fixe (il doit correspondre au nombre d'arguments dans le constructeur Key), votre solution est en fait pas mal.

Il est plus efficace et plus facile à lire sans les lambdas, cependant, comme:

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

Vous avez raison, les flux ne sont pas toujours la meilleure solution, cela a définitivement l'air mieux, merci. – szymonszymon

1

La solution canonique est d'utiliser flatMap. Cependant, la partie délicate consiste à créer l'objet Key à partir des multiples niveaux d'entrée.

L'approche directe est de faire l'évaluation dans la fonction la plus interne, où chaque valeur est portée

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

mais cela devient bientôt impossible avec l'imbrication profonde

Une solution sans imbrication profonde nécessite éléments pour contenir des valeurs de composés intermédiaires. Par exemple. si nous définissons Key comme

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)+')'; 
    } 
} 

(en supposant String comme type d'élément), on peut utiliser

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

Notez que ces flatMap étapes sont maintenant au même niveau, à savoir non plus imbriqué. En outre, toutes ces étapes sont identiques, ne différant que dans le créateur réel, ce qui conduit à la solution générale supportant un nombre arbitraire de Creator instances.

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

Ici, chaque créateur est la cartographie à la fonction de production flux identique de la solution précédente, alors tous sont réduits à une fonction unique combinant chaque fonction avec une étape flatMap à la suivante, et enfin la fonction résultante est exécuté pour obtenir un flux, qui est ensuite collecté à List.