2009-04-03 18 views
33

Kennen Sie einige nette Java-Bibliotheken, die es Ihnen ermöglichen, kartesische Produkte aus zwei (oder mehr) Mengen zu erstellen?Kartesisches Produkt beliebiger Mengen in Java

Zum Beispiel: Ich habe drei Sätze. Eines mit Objekten der Klasse Person, das zweite mit Objekten der Klasse Geschenk und das dritte mit Objekten der Klasse GeschenkExtension.

Ich möchte ein Set mit allen möglichen dreifachen Person-Gift-GiftExtension generieren.

Die Anzahl der Sätze kann variieren, so dass ich dies nicht in verschachtelten foreach-Schleife tun kann. Unter bestimmten Bedingungen meine Anwendung ein Produkt von Person-Geschenk Paar machen muss, ist es manchmal triple Person-Gift-GiftExtension, manchmal kann es sogar Sätze Person-Geschenk-GiftExtension-GiftSecondExtension-GiftThirdExtension usw. sein

+0

Können Sie näher erläutern, was genau Sie erreichen möchten? – Rik

+0

Diese Frage ist aus theoretischer Sicht sehr interessant. Ich war überrascht, wie schwierig es ist, eine saubere Lösung für eine einfache Frage wie diese zu finden - wenn ich eine gefunden hätte, hätte ich geantwortet. Aber wenn das gesagt wird ... –

+0

... scheint Ihre Frage auf eine bestimmte Anwendung zu zielen, und es scheint mir so, als würden Sie alle Arten verlieren, wenn Sie einfach alles in Sätze und diese Sätze in ein kartesisches Produkt stanzen. Vielleicht ist Ihre Herangehensweise ernsthaft fehlerhaft, wenn Sie an mathematische und wenige an OOP denken? –

Antwort

30

Bearbeiten: Vorherige Lösungen für zwei Sätze entfernt. Siehe Bearbeitungsverlauf für Details.

Hier ist eine Möglichkeit, es rekursiv für eine beliebige Anzahl von Sätzen zu tun:

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; 
} 

Beachten Sie, dass es unmöglich ist, mit den zurückgegebenen Sätzen jede generische Typinformationen zu halten. Wenn Sie im Voraus wissen, wie viele Mengen Sie das Produkt verwenden möchten, können Sie ein generisches Tupel definieren, das so viele Elemente enthält (zum Beispiel Triple<A, B, C>), aber es gibt keine Möglichkeit, eine beliebige Anzahl generischer Parameter in Java zu haben.

+0

Ich denke, das ist ein wirklich guter Weg, um mit Paaren umzugehen. Wenn er nicht weiß, ob er vielleicht Paare, Tripel, Quadrupel braucht ... dann passt es nicht direkt, aber er könnte Paar , GiftExtension> ich denke. –

11

Die Anzahl der Sätze kann variieren, so dass ich dies nicht in verschachtelten foreach-Schleife tun kann.

Zwei Hinweise:

  • A x B x C = A x (B x C)
  • Rekursion
1

Der Speicher (und Verarbeitung) Fußabdrucks für ein kartesisches Produkt benötigt kann ziemlich schnell außer Kontrolle geraten. Die naive Implementierung kann Speicher verschwenden und viel Zeit in Anspruch nehmen. Es wäre schön, die Operationen zu kennen, die Sie in einem solchen Set durchführen möchten, um eine Implementierungsstrategie vorzuschlagen.

In jedem Fall, tun Sie etwas wie Sets.SetView auf Google Sammlungen. Dies ist eine Menge, die von anderen Sets unterstützt wird, wenn sie hinzugefügt werden. Die Idee für ihre Problem gibt es, den Aufruf addAll zu vermeiden. Die Idee für Ihr Problem ist zu vermeiden, dass NxMxK ​​zu einem Set hinzugefügt wird.

Google collections can be found here und die genannte Klasse ist here

2

Ja, es Functional Java ist.

Für ein Set (s):

s.bind (P.p2(), s);

+0

Beachten Sie, dass fj.data.Set keine Bind-Methode hat, aber es muss toStream() und iterableSet (Iterable) haben, um in/aus fj.data.Stream zu konvertieren, das eine Bind-Methode hat. – Apocalisp

17

Das Verfahren unten erstellt das kartesische Produkt aus einer Liste von Liste von Strings:

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; 
} 

Beispiel:

[[Apple, Red], [Apple, Green], [Apple, Blue], [Banana, Red], [Banana, Green], [Banana, Blue]] 
+0

würde die Zeit Komplexität dafür sein, O (n)^2 – j2emanue

4

Hier ist ein Iterable:

System.out.println(cartesianProduct(Arrays.asList(Arrays.asList("Apple", "Banana"), Arrays.asList("Red", "Green", "Blue")))); 

dies ergäbe , mit dem Sie eine vereinfachte For-Schleife verwenden können:

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

Wie wird es gemacht? Wir brauchen ein Iterable, um die vereinfachte For-Schleife zu verwenden, und ein Iterator muss vom Iterable zurückgegeben werden. Wir geben eine Liste von Objekten zurück - dies könnte ein Set anstelle von List sein, aber Set hat keinen indizierten Zugriff, daher wäre es etwas komplizierter, es mit Set statt List zu implementieren. Anstelle einer generischen Lösung wäre Objekt für viele Zwecke in Ordnung gewesen, aber Generika erlauben mehr Einschränkungen.

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; 
      } 
     } 
    } 
} 

Die mathematische Arbeit erfolgt in der 'get'-Methode. Denken Sie über 2 Sätze von 10 Elementen nach. Sie haben insgesamt 100 Kombinationen, die von 00, 01, 02, ... 10, ... bis 99 gezählt werden. Für 5 X 10 Elemente 50, für 2 X 3 Elemente 6 Kombinationen. Der Modulo der Größe der Unterliste hilft, ein Element für jede Iteration auszuwählen.

Iterable i die am wenigsten interessante Sache hier:

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

Zur Umsetzung Iterable, die die für-jede Art von Schleife erlaubt, müssen wir iterator() implementieren, und für Iterator haben wir hasNext zu implementieren (), next() und remove().

Ergebnis:

(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,) 
8

Index-basierte Lösung

mit den Indizes zu arbeiten, ist eine Alternative, die eine schnelle und speichereffizient ist und eine beliebige Anzahl von Sätzen umgehen kann. Die Implementierung von Iterable ermöglicht eine einfache Verwendung in einer for-each-Schleife. Ein Beispiel für die Verwendung finden Sie in der Methode #main.

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]]); 
     } 
    } 
} 
16

Dies ist eine ziemlich alte Frage, aber warum nicht Guava's cartesianProduct verwenden? Hier

+3

Weil das wurde implementiert, nachdem die Frage veröffentlicht wurde. Siehe http://stackoverflow.com/a/1723050/600500. –

+0

Link rot ist passiert. – Tuupertunut

+0

Derzeit ist dies die einfachere Lösung;) –

0

ist ein Iterator, die das kartesische Produkt aus einer zweidimensionalen Anordnung gibt, wo die Arrays Komponenten, die die Sätze von der Frage stellen (ein immer tatsächliche Set s auf Arrays umwandeln kann):

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; 
    } 
} 

The Grundidee ist zu behandeln count als eine Multi-Radix-Nummer (Ziffer i hat seine eigene Radix, die die Länge der i 'th "Satz" entspricht). Wann immer wir next auflösen müssen (dh wenn hasNext() angerufen wird und nextnull ist), zerlegen wir die Zahl in ihre Ziffern in diesem Multi-Radix. Diese Ziffern werden jetzt als die Indizes verwendet, aus denen wir Elemente aus den verschiedenen Mengen zeichnen.

Beispiel Verwendung:

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

Output:

[a, X, r] 
[b, X, r] 
[c, X, r] 
[a, X, s] 
[b, X, s] 
[c, X, s] 

Wenn ein Arrays nicht mag, ist der Code trivially umwandelbar in Sammlungen verwenden.

Ich denke, das ist mehr oder weniger ähnlich der Antwort von "Benutzer unbekannt", nur ohne Rekursion und Sammlungen.

Verwandte Themen