Connaissez-vous des librairies jolies Java qui vous permettent de créer un produit cartésien à partir de deux ensembles (ou plus)?
Par exemple: j'ai trois ensembles. Une avec des objets de classe Person, une seconde avec des objets de classe Gift et une troisième avec des objets de classe GiftExtension.
Je veux générer un ensemble contenant tous les triples possibles Person-Gift-GiftExtension.
Le nombre d'ensembles peut varier, je ne peux donc pas faire cela dans une boucle imbriquée. Dans certaines conditions, mon application doit créer un produit de paire Personne-Cadeau, parfois triple, personne-cadeau-cadeau-extension-extension, parfois même des ensembles Personne-cadeau-cadeau-extension-cadeau-seconde-extension-cadeau-troisième-extension, etc.
Edit: Les solutions précédentes pour deux ensembles ont été supprimées. Voir l'historique d'édition pour plus de détails.
Voici un moyen de le faire de manière récursive pour un nombre arbitraire d'ensembles:
public static Set<Set<Object>> cartesianProduct(Set<?>... sets) {
if (sets.length < 2)
throw new IllegalArgumentException(
"Can't have a product of fewer than two sets (got " +
sets.length + ")");
return _cartesianProduct(0, sets);
}
private static Set<Set<Object>> _cartesianProduct(int index, Set<?>... sets) {
Set<Set<Object>> ret = new HashSet<Set<Object>>();
if (index == sets.length) {
ret.add(new HashSet<Object>());
} else {
for (Object obj : sets[index]) {
for (Set<Object> set : _cartesianProduct(index+1, sets)) {
set.add(obj);
ret.add(set);
}
}
}
return ret;
}
Notez qu'il est impossible de conserver des informations de type génériques avec les ensembles renvoyés. Si vous saviez à l'avance combien d'ensembles vous souhaitez utiliser le produit, vous pouvez définir un Tuple générique pour contenir autant d'éléments (par exemple Triple<A, B, C>
), mais il n'existe aucun moyen d'avoir un nombre arbitraire de paramètres génériques en Java.
C'est une question assez ancienne, mais pourquoi ne pas utiliser le produit cartésien de Guoy ?
La méthode ci-dessous crée le produit cartésien d'une liste de chaînes:
protected <T> List<List<T>> cartesianProduct(List<List<T>> lists) {
List<List<T>> resultLists = new ArrayList<List<T>>();
if (lists.size() == 0) {
resultLists.add(new ArrayList<T>());
return resultLists;
} else {
List<T> firstList = lists.get(0);
List<List<T>> remainingLists = cartesianProduct(lists.subList(1, lists.size()));
for (T condition : firstList) {
for (List<T> remainingList : remainingLists) {
ArrayList<T> resultList = new ArrayList<T>();
resultList.add(condition);
resultList.addAll(remainingList);
resultLists.add(resultList);
}
}
}
return resultLists;
}
Exemple:
System.out.println(cartesianProduct(Arrays.asList(Arrays.asList("Apple", "Banana"), Arrays.asList("Red", "Green", "Blue"))));
donnerait ceci:
[[Apple, Red], [Apple, Green], [Apple, Blue], [Banana, Red], [Banana, Green], [Banana, Blue]]
Le nombre d'ensembles peut varier alors je ne peut pas faire cela dans une boucle foreach imbriquée.
Deux astuces:
Solution indexée
L'utilisation des index est une alternative rapide, économe en mémoire et capable de gérer un nombre illimité d'ensembles. L'implémentation d'Iterable permet une utilisation facile dans une boucle pour chaque boucle. Voir la méthode #main pour un exemple d'utilisation.
public class CartesianProduct implements Iterable<int[]>, Iterator<int[]> {
private final int[] _lengths;
private final int[] _indices;
private boolean _hasNext = true;
public CartesianProduct(int[] lengths) {
_lengths = lengths;
_indices = new int[lengths.length];
}
public boolean hasNext() {
return _hasNext;
}
public int[] next() {
int[] result = Arrays.copyOf(_indices, _indices.length);
for (int i = _indices.length - 1; i >= 0; i--) {
if (_indices[i] == _lengths[i] - 1) {
_indices[i] = 0;
if (i == 0) {
_hasNext = false;
}
} else {
_indices[i]++;
break;
}
}
return result;
}
public Iterator<int[]> iterator() {
return this;
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Usage example. Prints out
*
* <pre>
* [0, 0, 0] a, NANOSECONDS, 1
* [0, 0, 1] a, NANOSECONDS, 2
* [0, 0, 2] a, NANOSECONDS, 3
* [0, 0, 3] a, NANOSECONDS, 4
* [0, 1, 0] a, MICROSECONDS, 1
* [0, 1, 1] a, MICROSECONDS, 2
* [0, 1, 2] a, MICROSECONDS, 3
* [0, 1, 3] a, MICROSECONDS, 4
* [0, 2, 0] a, MILLISECONDS, 1
* [0, 2, 1] a, MILLISECONDS, 2
* [0, 2, 2] a, MILLISECONDS, 3
* [0, 2, 3] a, MILLISECONDS, 4
* [0, 3, 0] a, SECONDS, 1
* [0, 3, 1] a, SECONDS, 2
* [0, 3, 2] a, SECONDS, 3
* [0, 3, 3] a, SECONDS, 4
* [0, 4, 0] a, MINUTES, 1
* [0, 4, 1] a, MINUTES, 2
* ...
* </pre>
*/
public static void main(String[] args) {
String[] list1 = { "a", "b", "c", };
TimeUnit[] list2 = TimeUnit.values();
int[] list3 = new int[] { 1, 2, 3, 4 };
int[] lengths = new int[] { list1.length, list2.length, list3.length };
for (int[] indices : new CartesianProduct(lengths)) {
System.out.println(Arrays.toString(indices) //
+ " " + list1[indices[0]] //
+ ", " + list2[indices[1]] //
+ ", " + list3[indices[2]]);
}
}
}
Voici un Iterable, qui vous permet d'utiliser une boucle for simplifiée:
import Java.util.*;
// let's begin with the demo. Instead of Person and Gift,
// I use the well known char and int.
class CartesianIteratorTest {
public static void main (String[] args) {
List <Object> lc = Arrays.asList (new Object [] {'A', 'B', 'C', 'D'});
List <Object> lC = Arrays.asList (new Object [] {'a', 'b', 'c'});
List <Object> li = Arrays.asList (new Object [] {1, 2, 3, 4});
// sometimes, a generic solution like List <List <String>>
// might be possible to use - typically, a mixture of types is
// the common nominator
List <List <Object>> llo = new ArrayList <List <Object>> ();
llo.add (lc);
llo.add (lC);
llo.add (li);
// Preparing the List of Lists is some work, but then ...
CartesianIterable <Object> ci = new CartesianIterable <Object> (llo);
for (List <Object> lo: ci)
show (lo);
}
public static void show (List <Object> lo) {
System.out.print ("(");
for (Object o: lo)
System.out.print (o + ", ");
System.out.println (")");
}
}
Comment est-il fait? Nous avons besoin d'un Iterable, pour utiliser la boucle for simplifiée, et un Iterator doit être renvoyé à partir d'Iterable. Nous renvoyons une liste d'objets - il peut s'agir d'un ensemble au lieu de liste, mais l'ensemble n'a pas d'accès indexé; il serait donc un peu plus compliqué de l'implémenter avec Set au lieu de la liste. Au lieu d’une solution générique, Object aurait fonctionné à de nombreuses fins, mais les génériques permettaient davantage de restrictions.
class CartesianIterator <T> implements Iterator <List <T>> {
private final List <List <T>> lilio;
private int current = 0;
private final long last;
public CartesianIterator (final List <List <T>> llo) {
lilio = llo;
long product = 1L;
for (List <T> lio: lilio)
product *= lio.size ();
last = product;
}
public boolean hasNext () {
return current != last;
}
public List <T> next () {
++current;
return get (current - 1, lilio);
}
public void remove () {
++current;
}
private List<T> get (final int n, final List <List <T>> lili) {
switch (lili.size ())
{
case 0: return new ArrayList <T> (); // no break past return;
default: {
List <T> inner = lili.get (0);
List <T> lo = new ArrayList <T> ();
lo.add (inner.get (n % inner.size ()));
lo.addAll (get (n / inner.size (), lili.subList (1, lili.size ())));
return lo;
}
}
}
}
Le travail mathématique est effectué dans la méthode 'get'. Pensez à 2 séries de 10 éléments. Vous avez un total de 100 combinaisons, énumérées de 00, 01, 02, ... 10, ... à 99. Pour 5 x 10 éléments 50, pour 2 x 3 éléments, 6 combinaisons. Le modulo de la taille de la sous-liste aide à choisir un élément pour chaque itération.
Iterable i la chose la moins intéressante ici:
class CartesianIterable <T> implements Iterable <List <T>> {
private List <List <T>> lilio;
public CartesianIterable (List <List <T>> llo) {
lilio = llo;
}
public Iterator <List <T>> iterator () {
return new CartesianIterator <T> (lilio);
}
}
Pour implémenter Iterable, qui autorise le type de boucle pour chaque type, nous devons implémenter iterator (), et pour Iterator nous devons implémenter hasNext (), next () et remove ().
Résultat:
(A, a, 1, )
(B, a, 1, )
(C, a, 1, )
(D, a, 1, )
(A, b, 1, )
(B, b, 1, )
(C, b, 1, )
(D, b, 1, )
...
(A, a, 2, )
...
(C, c, 4, )
(D, c, 4, )
L'encombrement de la mémoire (et du traitement) nécessaire pour un produit cartésien peut rapidement déraper. L'implémentation naïve peut épuiser la mémoire et prendre beaucoup de temps. Il serait bien de connaître les opérations que vous envisagez de réaliser dans un tel ensemble, afin de suggérer une stratégie de mise en œuvre.
Dans tous les cas, faites quelque chose comme Sets.SetView sur les collections Google. Ceci est un ensemble qui est soutenu par d'autres ensembles à mesure qu'ils sont ajoutés. L'idée pour leur problème est d'éviter l'appel addAll. L'idée de votre problème est d'éviter que NxMxK ajoute à un ensemble.
Les collections de Google peuvent être trouvées ici et la classe mentionnée est ici
Voici une Iterator
qui donne le produit cartésien d'un tableau à deux dimensions, où les composants de tableaux représentent les ensembles de la question (on peut toujours convertir les Set
s réelles en tableaux):
public class CartesianIterator<T> implements Iterator<T[]> {
private final T[][] sets;
private final IntFunction<T[]> arrayConstructor;
private int count = 0;
private T[] next = null;
public CartesianIterator(T[][] sets, IntFunction<T[]> arrayConstructor) {
Objects.requireNonNull(sets);
Objects.requireNonNull(arrayConstructor);
this.sets = copySets(sets);
this.arrayConstructor = arrayConstructor;
}
private static <T> T[][] copySets(T[][] sets) {
// If any of the arrays are empty, then the entire iterator is empty.
// This prevents division by zero in `hasNext`.
for (T[] set : sets) {
if (set.length == 0) {
return Arrays.copyOf(sets, 0);
}
}
return sets.clone();
}
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
int tmp = count;
T[] value = arrayConstructor.apply(sets.length);
for (int i = 0; i < value.length; i++) {
T[] set = sets[i];
int radix = set.length;
int index = tmp % radix;
value[i] = set[index];
tmp /= radix;
}
if (tmp != 0) {
// Overflow.
return false;
}
next = value;
count++;
return true;
}
@Override
public T[] next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T[] tmp = next;
next = null;
return tmp;
}
}
L'idée de base est de traiter count
comme un nombre à radix multiples (le chiffre i
a son propre radix qui est égal à la longueur de la i
'th "set"). Chaque fois que nous devons résoudre next
(c'est-à-dire, lorsque hasNext()
est appelé et que next
est null
), nous décomposons le nombre en chiffres dans ce multi-radix. Ces chiffres sont maintenant utilisés comme index à partir desquels nous tirons des éléments des différents ensembles.
Exemple d'utilisation:
String[] a = { "a", "b", "c"};
String[] b = { "X" };
String[] c = { "r", "s" };
String[][] abc = { a, b, c };
Iterable<String[]> it = () -> new CartesianIterator<>(abc, String[]::new);
for (String[] s : it) {
System.out.println(Arrays.toString(s));
}
Sortie:
[a, X, r]
[b, X, r]
[c, X, r]
[a, X, s]
[b, X, s]
[c, X, s]
Si on n'aime pas les tableaux, le code est trivialement convertible en utilisant des collections.
Je suppose que cela ressemble plus ou moins à la réponse donnée par "utilisateur inconnu", mais sans récursion ni collections.