2009-04-03 32 views
33

Bạn có biết một số libaries Java gọn gàng cho phép bạn tạo ra sản phẩm Descartes của hai (hoặc nhiều) bộ?Sản phẩm Descartes của các bộ tùy ý trong Java

Ví dụ: Tôi có ba bộ. Một với các đối tượng của lớp Person, thứ hai với các đối tượng của lớp Gift và thứ ba với các đối tượng của lớp GiftExtension.

Tôi muốn tạo một bộ chứa tất cả bộ ba có thể Person-Gift-GiftExtension.

Số lượng bộ có thể thay đổi nên tôi không thể thực hiện việc này trong vòng lặp forested lồng nhau. Trong một số điều kiện ứng dụng của tôi cần phải thực hiện một sản phẩm của cặp Person-quà tặng, đôi khi nó là ba Person-Gift-GiftExtension, đôi khi có thậm chí có thể là bộ Person-Gift-GiftExtension-GiftSecondExtension-GiftThirdExtension vv

+0

Bạn có thể giải thích chính xác những gì bạn đang cố gắng hoàn thành không? – Rik

+0

Câu hỏi này rất thú vị từ một nhà lý thuyết, quan điểm học thuật. Tôi rất ngạc nhiên khi tìm ra giải pháp sạch cho một câu hỏi dễ như thế này - nếu tôi đã tìm được một câu hỏi, tôi sẽ trả lời. Nhưng có điều này nói ... –

+0

... Câu hỏi của bạn dường như nhắm mục tiêu một ứng dụng cụ thể, và có vẻ như tôi sẽ mất tất cả các loại nếu bạn chỉ đục mọi thứ vào bộ và những bộ đó thành một sản phẩm Descartes. Có lẽ cách tiếp cận của bạn là thiếu sót nghiêm trọng bằng cách suy nghĩ đến toán học và một vài về mặt OOP? –

Trả lời

30

Chỉnh sửa: Giải pháp trước cho hai bộ đã bị xóa. Xem lịch sử chỉnh sửa để biết chi tiết.

Dưới đây là một cách để làm điều đó một cách đệ quy cho một số tùy ý các bộ:

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

Lưu ý rằng không thể giữ bất kỳ loại thông tin chung với các bộ trả lại. Nếu bạn biết trước bao nhiêu bộ bạn muốn lấy sản phẩm, bạn có thể định nghĩa một bộ dữ liệu chung để chứa nhiều phần tử đó (ví dụ Triple<A, B, C>), nhưng không có cách nào để có một số tham số chung tùy ý trong Java.

+0

Tôi nghĩ đây là một cách tuyệt vời để đối phó với các cặp. Nếu anh ta không biết nếu anh ta có thể cần cặp, ba, tứ bốn ... thì nó không trực tiếp phù hợp, nhưng anh ta có thể sử dụng Pair , GiftExtension> tôi nghĩ. –

11

Số lượng bộ có thể khác nhau vì vậy tôi không thể thực hiện việc này trong vòng lặp forested lồng nhau.

Hai gợi ý:

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

Bộ nhớ (và chế biến) dấu chân cần thiết cho một sản phẩm Descartes có thể thoát ra khỏi tay khá nhanh. Việc thực hiện ngây thơ có thể xả bộ nhớ và mất rất nhiều thời gian. Thật tuyệt khi biết các hoạt động bạn đang lên kế hoạch thực hiện trong một bộ như vậy, để đề xuất một chiến lược thực hiện.

Trong mọi trường hợp, hãy làm điều gì đó như Sets.SetView trên bộ sưu tập của google. Đây là tập hợp được các bộ khác hỗ trợ khi chúng được thêm vào. Ý tưởng cho vấn đề của họ có để tránh cuộc gọi addAll. Ý tưởng cho vấn đề của bạn là tránh làm cho NxMxK ​​thêm vào tập hợp.

Google collections can be found here và lớp đề cập là here

2

Vâng, có Functional Java.

Đối với (các) bộ:

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

+0

lưu ý rằng fj.data.Set không có phương thức bind, nhưng nó có toStream() và iterableSet (Iterable) để chuyển đổi sang/từ fj.data.Stream mà có một phương thức bind. – Apocalisp

17

Phương pháp dưới đây tạo ra sản phẩm Cartesian của một danh sách các danh sách các chuỗi:

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

Ví dụ:

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

sẽ mang lại điều này:

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

sẽ phức tạp về thời gian cho việc này là O (n)^2 – j2emanue

4

Dưới đây là một Iterable , cho phép bạn sử dụng đơn giản cho vòng lặp:

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

Làm cách nào? Chúng ta cần một Iterable, để sử dụng đơn giản cho vòng lặp, và một Iterator phải được trả về từ Iterable. Chúng tôi trả về một Danh sách các đối tượng - đây có thể là một Tập thay vì Danh sách, nhưng Bộ không có quyền truy cập được lập chỉ mục, do đó sẽ phức tạp hơn một chút, để triển khai thực hiện bằng Đặt thay vì Danh sách. Thay vì một giải pháp chung chung, Object sẽ tốt cho nhiều mục đích, nhưng generics cho phép hạn chế hơn.

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

Công việc toán học được thực hiện trong phương pháp 'get'. Hãy suy nghĩ về 2 bộ của 10 yếu tố. Bạn có tổng cộng 100 kết hợp, được liệt kê từ 00, 01, 02, ... 10, ... đến 99. Đối với 5 X 10 phần tử 50, cho 2 X 3 phần tử 6 kết hợp. Modulo của kích thước danh sách con giúp chọn một phần tử cho mỗi lần lặp.

Iterable i điều thú vị nhất ở đây:

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

Để thực hiện Iterable, cho phép người for-each loại vòng lặp, chúng ta phải thực hiện iterator(), và cho Iterator chúng ta phải thực hiện hasNext (), next() và remove().

Kết quả:

(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 dựa trên giải pháp

Làm việc với các chỉ số là một sự thay thế đó là nhanh và bộ nhớ hiệu quả và có thể xử lý bất kỳ số lượng bộ. Thực hiện Iterable cho phép dễ dàng sử dụng trong một vòng lặp for-each. Xem phương thứC#main cho ví dụ sử dụng.

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

Đây là câu hỏi khá cũ, nhưng tại sao không sử dụng Guava's cartesianProduct?

+3

Vì điều đó đã được triển khai sau khi câu hỏi được đăng. Xem http://stackoverflow.com/a/1723050/600500. –

+0

Hối liên kết đã xảy ra. – Tuupertunut

+0

Hiện tại, đây là giải pháp dễ dàng hơn;) –

0

Dưới đây là một Iterator cung cấp cho sản phẩm Cartesian của một mảng hai chiều, nơi mà các thành phần mảng đại diện cho các bộ từ câu hỏi (một luôn có thể chuyển đổi thực tế Set s để mảng):

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

Các ý tưởng cơ bản là xử lý count dưới dạng một số đa số (số i có cơ số riêng của nó tương đương với chiều dài của bộ số i 'th'). Bất cứ khi nào chúng tôi phải giải quyết next (nghĩa là, khi hasNext() được gọi và nextnull), chúng tôi phân chia số thành các chữ số của nó trong multi-radix này. Các chữ số này hiện được sử dụng làm chỉ mục mà từ đó chúng tôi vẽ các phần tử từ các tập hợp khác nhau.

Ví dụ sử dụng:

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] 

Nếu chúng ta không thích mảng, mã là trivially mui trần vào sử dụng các bộ sưu tập.

Tôi đoán điều này ít nhiều giống với câu trả lời được đưa ra bởi "người dùng không xác định", chỉ không có đệ quy và bộ sưu tập.

Các vấn đề liên quan