Cartesian power (a special Cartesian product) — choose elements from array, in repeatable style

為{幸葍}努か 提交于 2019-12-08 07:09:02

问题


Input:
There is an input array (assume there are no repeated elements) of length n.

Output:
And would like to print all possible array of the same length n consists of elements from input array, each input element could be used multiple times in the output array.

Tips:

  • n is variable among multiple inputs.

For example

Input array: [0, 1, 2]

Expected output:

  • 000
  • 001
  • 002
  • 010
  • 011
  • 012
  • 020
  • 021
  • 022

  • 100

  • 101
  • 102
  • 110
  • 111
  • 112
  • 120
  • 121
  • 122

  • 200

  • 201
  • 202
  • 210
  • 211
  • 212
  • 220
  • 221
  • 222

There are 3 * 3 * 3 = 27 combinations, or in general n^n for array with length n.


Questions

  • How to print this for small input n (<=5), probably in recursive style?
  • How to print this for large input n efficiently, without overflow in stack, probably with a single loop?
    e.g when n = 9, there are 387420489 outputs, the program should be able to handle such inputs.
  • What should this problem be called ? It's not a typical permutation since the elements are repeatable. Modification to the title is welcomed.

回答1:


A single loop is hard. Recursive might be the most intuitive approach. But here is an iterative version with two loops, using Python but avoiding magic functionality from itertools so it's not all that language-specific:

a = [0]*n
while(True):
  for i in range(n):
    a[i] += 1
    if a[i] == n:
      a[i] = 0
    else:
      break
  else:
    break
  print("".join(map(str, reversed(a))))

Idea is the following: index your numbers from right to left (thus the reversed call in there). Increment the righternmost digit. As long as you hit the limit of n there, reset to zero but increment the digit one further left. This is essentially adding 1 using long addition with carry. Exit the inner loop when there is no more carry. Exit the outer loop when you don't explicitly exit the inner one, i.e. when you have a carry for all digits.

Test run at https://ideone.com/n0ScW8.

Actually now that I look at the code again, I see a way to use a single loop, and avoid Python's for-else construct which has no obvious counterpart in some other languages. Let's use JavaScript this time, for a change.

a = Array.from(Array(n), x=>0);
i = n - 1;
while (i >= 0) {
  a[i]++;
  if (a[i] == n) {
    a[i] = 0;
    i--;
  } else {
    console.log(a.join(""));
    i = n - 1;
  }
}

Note that both versions omit the initial 000…0 solution, so they are short one solution. Easy to add that up front. Also notice that I misread the question, so I'm assuming an array of numbers 0 through n-1 while in fact you asked for arbitrary input. But a simple indirection will turn one into the other, so I leave that as an exercise.




回答2:


I wrote an Java implementation which include several algorithms:

  • Radix conversion.
  • Iteration (according to @MvG's answer, from this question: https://stackoverflow.com/a/52979675).
  • Recursion (according to @MBo's answer, from an additional question: https://stackoverflow.com/a/53013628).

Code - implementation

CartesianPower.java:

import java.util.Arrays;

/**
 * Cartesian power of n elements.
 * <h2>Refer:</h2>
 * <li><a href="https://stackoverflow.com/questions/52977819">Original question, with radix & iteration solution.</a>
 * <li><a href="https://stackoverflow.com/questions/53012861">Additional question, with recursion solution.</a>
 * 
 * <h2>Algorithms:</h2>
 * <li><b>Radix</b>: intuitive, but not very efficient,
 * <li><b>Iteration</b>: best choice, most efficient & scalable,
 * <li><b>Recursion</b>: elegant, but not very intuitive, and performance is bad (or even stack overflow) for large input size,
 * <li>
 * 
 * @author eric
 * @date Oct 25, 2018 4:53:34 PM
 */
public class CartesianPower {
    public static final int MIN_ARR_LEN = 2; // min length of input array ,
    public static final int MAX_ARR_LEN = 10; // max length of input array,

    public static final int ARR_PRINT_LEN_LIMIT = 6; // max array length, that is allowed to print detailed output items to console,

    /**
     * Print cartesian power of array elements, by converting between radix.
     * <p>
     * time complexity: O(n) for each output item, and O(n^(n+1)) overall.
     * 
     * @param arr
     *            an array with length between [Character.MIN_RADIX, Character.MAX_RADIX]
     * @return count of cartesian, or -1 if radix is out of range,
     */
    public static <T> long cartesianPowerViaRadix(T arr[]) {
        int len = arr.length; // length of array, also the radix,
        // check radix range,
        if (len < MIN_ARR_LEN || len > MAX_ARR_LEN) {
            System.out.printf("%d is out of range [%d, %d]\n", len, MIN_ARR_LEN, MAX_ARR_LEN);
            return -1;
        }

        long count = 0;
        long totalCount = (long) Math.pow(len, len);
        T tmpArr[] = Arrays.copyOf(arr, len);

        while (count < totalCount) {
            String baseStr = Long.toString(count, len);

            for (int i = 0; i < baseStr.length(); i++) {
                char c = baseStr.charAt(i);
                int r = Integer.parseInt(String.valueOf(c), len);
                tmpArr[i] = arr[r];
            }

            for (int i = baseStr.length(); i < len; i++) {
                tmpArr[i] = arr[0];
            }

            printArr(tmpArr);
            count++;
        }

        System.out.printf("input arr: %s, total count: %d\n", Arrays.toString(arr), count);
        return count;
    }

    /**
     * Print cartesian power of array elements, by a single iteration.
     * <p>
     * time complexity:
     * <li>When don't print to console
     * <p>
     * O(1) for each output item, and O(n^n) overall.
     * <li>When print to console
     * <p>
     * O(n) for each output item, and O(n^(n+1)) overall.
     * 
     * @param n
     *            array length, array is assumed as numbers start from 0 and increase by 1, e.g [0, 1, 2],
     *            <p>
     *            it should be within range [CartesianProduct.MIN_ARR_LEN, CartesianProduct.MAX_ARR_LEN]
     * @return count of cartesian, or -1 if array length is out of range,
     */
    public static long cartesianPowerViaIterationN(int n) {
        // check range of array length,
        if (n < MIN_ARR_LEN || n > MAX_ARR_LEN) {
            System.out.printf("input length [%d] is out of range [%d, %d]\n", n, MIN_ARR_LEN, MAX_ARR_LEN);
            return -1;
        }

        long startTime = System.currentTimeMillis();

        long count = 0;
        int tmpArr[] = new int[n];
        Arrays.fill(tmpArr, 0);

        // initial case,
        printArr(tmpArr); // all elements as 0,
        count++;

        int i = n - 1;
        while (i >= 0) {
            tmpArr[i]++;
            if (tmpArr[i] == n) {
                tmpArr[i] = 0;
                i--;
            } else {
                printArr(tmpArr);
                i = n - 1;
                count++;
            }
        }

        long during = (System.currentTimeMillis() - startTime); // during in milliseconds,
        System.out.printf("input arr length: %d, total count: %d, during: %d ms\n", n, count, during);
        return count;
    }

    /**
     * Print cartesian power of array elements, by a single iteration.
     * <p>
     * time complexity:
     * <li>When don't print to console
     * <p>
     * O(1) for each output item, and O(n^n) overall.
     * <li>When print to console
     * <p>
     * O(n) for each output item, and O(n^(n+1)) overall.
     * 
     * @param arr
     *            input array, could be of any type without order requirement,
     *            <p>
     *            its length should be within range [CartesianProduct.MIN_ARR_LEN, CartesianProduct.MAX_ARR_LEN]
     * @return count of cartesian, or -1 if array length is out of range,
     * @return
     */
    public static <T> long cartesianPowerViaIteration(T arr[]) {
        int n = arr.length;
        // check range of array length,
        if (n < MIN_ARR_LEN || n > MAX_ARR_LEN) {
            System.out.printf("input length [%d] is out of range [%d, %d]\n", n, MIN_ARR_LEN, MAX_ARR_LEN);
            return -1;
        }

        long startTime = System.currentTimeMillis();

        long count = 0;
        T tmpArr[] = Arrays.copyOf(arr, n); // tmp array,
        int idxArr[] = new int[n]; // index mapping array,
        Arrays.fill(idxArr, 0); // all indices as 0,

        // initial case,
        printIndirectionArr(idxArr, arr, tmpArr);
        count++;

        int i = n - 1;
        while (i >= 0) {
            idxArr[i]++;
            if (idxArr[i] == n) {
                idxArr[i] = 0;
                i--;
            } else {
                printIndirectionArr(idxArr, arr, tmpArr);
                i = n - 1;
                count++;
            }
        }

        long during = (System.currentTimeMillis() - startTime); // during in milliseconds,
        System.out.printf("input arr length: %d, total count: %d, during: %d ms\n", n, count, during);
        return count;
    }

    /**
     * Print cartesian power of array elements, via recursion, with raw int[] input.
     * <p>
     * Time complexity: O(1) for each output item, and O(n^n) overall.
     * 
     * @param arr
     * @return count of cartesian, or -1 if radix is out of range,
     */
    public static <T> long cartesianPowerViaRecursion(int arr[]) {
        int n = arr.length;
        // check range of array length,
        if (n < MIN_ARR_LEN || n > MAX_ARR_LEN) {
            System.out.printf("input length [%d] is out of range [%d, %d]\n", n, MIN_ARR_LEN, MAX_ARR_LEN);
            return -1;
        }

        long startTime = System.currentTimeMillis();
        int tmpArr[] = Arrays.copyOf(arr, n);

        long count = cartesianPowerViaRecursion(arr, tmpArr, 0);

        long during = (System.currentTimeMillis() - startTime); // during in milliseconds,
        System.out.printf("input arr length: %d, total count: %d, during: %d ms\n", n, count, during);

        return count;
    }

    /**
     * Print cartesian power of array elements, via recursion, the inner function, with raw int[] input.
     * 
     * @param arr
     *            input array,
     * @param tmpArr
     *            tmp array,
     * @param t
     *            current index in tmp array,
     * @return count of cartesian,
     */
    private static <T> long cartesianPowerViaRecursion(int arr[], int tmpArr[], int t) {
        int n = arr.length;
        long count = 0;

        if (t == n) {
            printArr(tmpArr);
            count++;
        } else {
            for (int i = 0; i < n; i++) {
                tmpArr[t] = arr[i];
                count += cartesianPowerViaRecursion(arr, tmpArr, t + 1);
            }
        }

        return count;
    }

    /**
     * Print cartesian power of array elements, via recursion.
     * <p>
     * Time complexity: O(1) for each output item, and O(n^n) overall.
     * 
     * @param arr
     * @return count of cartesian, or -1 if radix is out of range,
     */
    public static <T> long cartesianPowerViaRecursion(T arr[]) {
        int n = arr.length;
        // check range of array length,
        if (n < MIN_ARR_LEN || n > MAX_ARR_LEN) {
            System.out.printf("input length [%d] is out of range [%d, %d]\n", n, MIN_ARR_LEN, MAX_ARR_LEN);
            return -1;
        }

        long startTime = System.currentTimeMillis();
        T tmpArr[] = Arrays.copyOf(arr, n);

        long count = cartesianPowerViaRecursion(arr, tmpArr, 0);

        long during = (System.currentTimeMillis() - startTime); // during in milliseconds,
        System.out.printf("input arr length: %d, total count: %d, during: %d ms\n", n, count, during);

        return count;
    }

    /**
     * Print cartesian power of array elements, via recursion, the inner function.
     * 
     * @param arr
     *            input array,
     * @param tmpArr
     *            tmp array,
     * @param t
     *            current index in tmp array,
     * @return count of cartesian,
     */
    private static <T> long cartesianPowerViaRecursion(T arr[], T tmpArr[], int t) {
        int n = arr.length;
        long count = 0;

        if (t == n) {
            printArr(tmpArr);
            count++;
        } else {
            for (int i = 0; i < n; i++) {
                tmpArr[t] = arr[i];
                count += cartesianPowerViaRecursion(arr, tmpArr, t + 1);
            }
        }

        return count;
    }

    /**
     * Print int array, only when length <= limit.
     * 
     * @param arr
     * @return boolean, indicate whether printed,
     */
    private static boolean printArr(int arr[]) {
        if (arr.length > ARR_PRINT_LEN_LIMIT) {
            return false;
        }

        System.out.println(Arrays.toString(arr));
        return true;
    }

    /**
     * Print array, only when length <= limit.
     * 
     * @param arr
     * @return boolean, indicate whether printed,
     */
    private static <T> boolean printArr(T[] arr) {
        if (arr.length > ARR_PRINT_LEN_LIMIT) {
            return false;
        }

        System.out.println(Arrays.toString(arr));
        return true;
    }

    /**
     * Print indirection array, only when length <= limit.
     * 
     * @param idxArr
     *            contain index mapping,
     * @param arr
     * @param tmpArr
     * @return boolean, indicate whether printed,
     */
    private static <T> boolean printIndirectionArr(int idxArr[], T[] arr, T[] tmpArr) {
        if (arr.length > ARR_PRINT_LEN_LIMIT) {
            return false;
        }

        fillArrFromIndex(idxArr, arr, tmpArr); // fill tmp array,

        System.out.println(Arrays.toString(tmpArr));
        return true;
    }

    /**
     * Fill tmp array with elements from array, according to index mapping.
     * 
     * @param idxArr
     *            contain index mapping,
     * @param arr
     * @param tmpArr
     */
    private static <T> void fillArrFromIndex(int idxArr[], T[] arr, T[] tmpArr) {
        for (int i = 0; i < idxArr.length; i++) {
            tmpArr[i] = arr[idxArr[i]];
        }
    }
}

Tips:

  • Range of array length is [2, 10], to avoid running for too long.
  • Only when array length <= 6, will print the detailed output, otherwise only print a summary.

Code - test case

(all test case are using TestNG)

CartesianPowerRadixTest.java:
(test case - for radix algorithm)

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * CartesianPower radix - test.
 * 
 * @author eric
 * @date Oct 25, 2018 5:04:54 PM
 */
public class CartesianPowerRadixTest {
    @Test
    public void testOrderedNum() {
        Integer arr[] = new Integer[] { 0, 1, 2 }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRadix(arr), 27);
    }

    @Test
    public void testRandomNum() {
        Integer arr2[] = new Integer[] { 0, 2, 1 }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRadix(arr2), 27);

    }

    @Test
    public void testChar() {
        Character charArr[] = new Character[] { 'a', 'b', 'c' }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRadix(charArr), 27);
    }

    @Test
    public void testObject() {
        Object objectArr[] = new Object[] { 0, 'g', true }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRadix(objectArr), 27);
    }

    @Test
    public void testOutOfRange() {
        Object tooShortArr[] = new Object[] { 1 }; // len = 1,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRadix(tooShortArr), -1);

        Object tooLongArr[] = new Object[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                31, 32, 33, 34, 35, 36, 37, 38, 39 }; // len = 40,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRadix(tooLongArr), -1);
    }
}

CartesianPowerIterationNTest.java:
(test case - for iteration algorithm - input is length n, the array is assumed as int array start from 0 increase by 1)

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * CartesianPower iteration N - test.
 * 
 * @author eric
 * @date Oct 26, 2018 1:44:01 PM
 */
public class CartesianPowerIterationNTest {
    @Test
    public void testSmall() {
        int len = 3;
        Assert.assertEquals(CartesianPower.cartesianPowerViaIterationN(len), 27);
    }

    @Test
    public void testLarge() {
        int len = 9;
        Assert.assertEquals(CartesianPower.cartesianPowerViaIterationN(len), 387420489);
    }

    @Test
    public void testOutOfRange() {
        int tooShortLen = CartesianPower.MIN_ARR_LEN - 1;
        Assert.assertEquals(CartesianPower.cartesianPowerViaIterationN(tooShortLen), -1);

        int tooLongLen = CartesianPower.MAX_ARR_LEN + 1;
        Assert.assertEquals(CartesianPower.cartesianPowerViaIterationN(tooLongLen), -1);
    }
}

CartesianPowerIterationArrTest.java:
(test case - for iteration algorithm - input array is of any type without order requirement)

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * CartesianPower iteration array - test.
 * 
 * @author eric
 * @date Oct 26, 2018 2:48:50 PM
 */
public class CartesianPowerIterationArrTest {
    @Test
    public void testOrderedNum() {
        Integer arr[] = new Integer[] { 0, 1, 2 }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(arr), 27);
    }

    @Test
    public void testRandomNum() {
        Integer arr2[] = new Integer[] { 0, 2, 1 }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(arr2), 27);
    }

    @Test
    public void testChar() {
        Character charArr[] = new Character[] { 'a', 'b', 'c' }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(charArr), 27);
    }

    @Test
    public void testObject() {
        Object objectArr[] = new Object[] { 0, 'g', true }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(objectArr), 27);
    }

    @Test
    public void testLarge() {
        Object arr[] = new Object[] { 1, 0, 2, 'c', 'a', 'b', true, false, null }; // len = 9,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(arr), 387420489);

        // Object arr2[] = new Object[] { 1, 0, 2, 'c', 'a', 'b', true, false, null, new Object() }; // len = 10,
        // Assert.assertEquals(CartesianProduct.cartesianViaLoopArr(arr2), 10000000000L);
    }

    @Test
    public void testOutOfRange() {
        Object tooShortArr[] = new Object[] { 1 }; // len = 1,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(tooShortArr), -1);

        Object tooLongArr[] = new Object[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                31, 32, 33, 34, 35, 36, 37, 38, 39 }; // len = 40,
        Assert.assertEquals(CartesianPower.cartesianPowerViaIteration(tooLongArr), -1);
    }
}

CartesianPowerRecursiveRawTest.java:
(test case - for recursion algorithm - input array is of type int[])

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * CartesianPower recursion array - raw int[] input - test.
 * 
 * @author eric
 * @date Oct 27, 2018 4:09:18 AM
 */
public class CartesianPowerRecursiveRawTest {
    @Test
    public void testOrderedNum() {
        int arr[] = new int[] { 0, 1, 2 }; // len = 3
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(arr), 27);
    }

    @Test
    public void testRandomNum() {
        int arr2[] = new int[] { 0, 2, 1 }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(arr2), 27);
    }

    @Test
    public void testLarge() {
        int arr[] = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; // len = 9,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(arr), 387420489);

        // int arr2[] = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // len = 10,
        // Assert.assertEquals(CartesianPower.cartesianViaRecursion(arr2), 10000000000L);
    }

    @Test
    public void testOutOfRange() {
        int tooShortArr[] = new int[] { 1 }; // len = 1,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(tooShortArr), -1);

        int tooLongArr[] = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
                33, 34, 35, 36, 37, 38, 39 }; // len = 40,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(tooLongArr), -1);
    }
}

CartesianPowerRecursiveTest.java:
(test case - for recursion algorithm - input array is of generic type T[])

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * CartesianPower recursion array - test.
 * 
 * @author eric
 * @date Oct 26, 2018 11:45:27 PM
 */
public class CartesianPowerRecursiveTest {
    @Test
    public void testOrderedNum() {
        Integer arr[] = new Integer[] { 0, 1, 2 };
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(arr), 27);
    }

    @Test
    public void testRandomNum() {
        Integer arr2[] = new Integer[] { 0, 2, 1 }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(arr2), 27);
    }

    @Test
    public void testChar() {
        Character charArr[] = new Character[] { 'a', 'b', 'c' }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(charArr), 27);
    }

    @Test
    public void testObject() {
        Object objectArr[] = new Object[] { 0, 'g', true }; // len = 3,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(objectArr), 27);
    }

    @Test
    public void testLarge() {
        Object arr[] = new Object[] { 1, 0, 2, 'c', 'a', 'b', true, false, null }; // len = 9,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(arr), 387420489);

        // Object arr2[] = new Object[] { 1, 0, 2, 'c', 'a', 'b', true, false, null, new Object() }; // len = 10,
        // Assert.assertEquals(CartesianProduct.cartesianViaRecursion(arr2), 10000000000L);
    }

    @Test
    public void testOutOfRange() {
        Object tooShortArr[] = new Object[] { 1 }; // len = 1,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(tooShortArr), -1);

        Object tooLongArr[] = new Object[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
                31, 32, 33, 34, 35, 36, 37, 38, 39 }; // len = 40,
        Assert.assertEquals(CartesianPower.cartesianPowerViaRecursion(tooLongArr), -1);
    }
}


来源:https://stackoverflow.com/questions/52977819/cartesian-power-a-special-cartesian-product-choose-elements-from-array-in

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!