Cartesian product of arbitrary sets in Java

后端 未结 9 2207
小鲜肉
小鲜肉 2020-11-22 07:28

Do you know some neat Java libaries that allow you to make cartesian product of two (or more) sets?

For example: I have three sets. One with objects of class Person

9条回答
  •  Happy的楠姐
    2020-11-22 07:42

    Here is an Iterator that gives the cartesian product of a two-dimensional array, where the arrays components represent the sets from the question (one can always convert actual Sets to arrays):

    public class CartesianIterator implements Iterator {
        private final T[][] sets;
        private final IntFunction arrayConstructor;
    
        private int count = 0;
        private T[] next = null;
    
        public CartesianIterator(T[][] sets, IntFunction arrayConstructor) {
            Objects.requireNonNull(sets);
            Objects.requireNonNull(arrayConstructor);
    
            this.sets = copySets(sets);
            this.arrayConstructor = arrayConstructor;
        }
    
        private static  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 basic idea is to treat count as a multi-radix number (digit i has its own radix which equals the length of the i'th "set"). Whenever we have to resolve next (that is, when hasNext() is called and next is null), we decompose the number into its digits in this multi-radix. These digits are now used as the indices from which we draw elements from the different sets.

    Example use:

    String[] a = { "a", "b", "c"};
    String[] b = { "X" };
    String[] c = { "r", "s" };
    
    String[][] abc = { a, b, c };
    
    Iterable 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]
    

    If one doesn't like arrays, the code is trivially convertible into using collections.

    I guess this is more or less similar to the answer given by "user unknown", only without recursion and collections.

提交回复
热议问题