summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math3/util/Combinations.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/math3/util/Combinations.java')
-rw-r--r--src/main/java/org/apache/commons/math3/util/Combinations.java384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/util/Combinations.java b/src/main/java/org/apache/commons/math3/util/Combinations.java
new file mode 100644
index 0000000..8721dae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/util/Combinations.java
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math3.util;
+
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.OutOfRangeException;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Utility to create <a href="http://en.wikipedia.org/wiki/Combination">combinations</a> {@code (n,
+ * k)} of {@code k} elements in a set of {@code n} elements.
+ *
+ * @since 3.3
+ */
+public class Combinations implements Iterable<int[]> {
+ /** Size of the set from which combinations are drawn. */
+ private final int n;
+
+ /** Number of elements in each combination. */
+ private final int k;
+
+ /** Iteration order. */
+ private final IterationOrder iterationOrder;
+
+ /** Describes the type of iteration performed by the {@link #iterator() iterator}. */
+ private enum IterationOrder {
+ /** Lexicographic order. */
+ LEXICOGRAPHIC
+ }
+
+ /**
+ * Creates an instance whose range is the k-element subsets of {0, ..., n - 1} represented as
+ * {@code int[]} arrays.
+ *
+ * <p>The iteration order is lexicographic: the arrays returned by the {@link #iterator()
+ * iterator} are sorted in descending order and they are visited in lexicographic order with
+ * significance from right to left. For example, {@code new Combinations(4, 2).iterator()}
+ * returns an iterator that will generate the following sequence of arrays on successive calls
+ * to {@code next()}:<br>
+ * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} If {@code k == 0} an iterator
+ * containing an empty array is returned; if {@code k == n} an iterator containing [0, ..., n -
+ * 1] is returned.
+ *
+ * @param n Size of the set from which subsets are selected.
+ * @param k Size of the subsets to be enumerated.
+ * @throws org.apache.commons.math3.exception.NotPositiveException if {@code n < 0}.
+ * @throws org.apache.commons.math3.exception.NumberIsTooLargeException if {@code k > n}.
+ */
+ public Combinations(int n, int k) {
+ this(n, k, IterationOrder.LEXICOGRAPHIC);
+ }
+
+ /**
+ * Creates an instance whose range is the k-element subsets of {0, ..., n - 1} represented as
+ * {@code int[]} arrays.
+ *
+ * <p>If the {@code iterationOrder} argument is set to {@link IterationOrder#LEXICOGRAPHIC}, the
+ * arrays returned by the {@link #iterator() iterator} are sorted in descending order and they
+ * are visited in lexicographic order with significance from right to left. For example, {@code
+ * new Combinations(4, 2).iterator()} returns an iterator that will generate the following
+ * sequence of arrays on successive calls to {@code next()}:<br>
+ * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]} If {@code k == 0} an iterator
+ * containing an empty array is returned; if {@code k == n} an iterator containing [0, ..., n -
+ * 1] is returned.
+ *
+ * @param n Size of the set from which subsets are selected.
+ * @param k Size of the subsets to be enumerated.
+ * @param iterationOrder Specifies the {@link #iterator() iteration order}.
+ * @throws org.apache.commons.math3.exception.NotPositiveException if {@code n < 0}.
+ * @throws org.apache.commons.math3.exception.NumberIsTooLargeException if {@code k > n}.
+ */
+ private Combinations(int n, int k, IterationOrder iterationOrder) {
+ CombinatoricsUtils.checkBinomial(n, k);
+ this.n = n;
+ this.k = k;
+ this.iterationOrder = iterationOrder;
+ }
+
+ /**
+ * Gets the size of the set from which combinations are drawn.
+ *
+ * @return the size of the universe.
+ */
+ public int getN() {
+ return n;
+ }
+
+ /**
+ * Gets the number of elements in each combination.
+ *
+ * @return the size of the subsets to be enumerated.
+ */
+ public int getK() {
+ return k;
+ }
+
+ /** {@inheritDoc} */
+ public Iterator<int[]> iterator() {
+ if (k == 0 || k == n) {
+ return new SingletonIterator(MathArrays.natural(k));
+ }
+
+ switch (iterationOrder) {
+ case LEXICOGRAPHIC:
+ return new LexicographicIterator(n, k);
+ default:
+ throw new MathInternalError(); // Should never happen.
+ }
+ }
+
+ /**
+ * Defines a lexicographic ordering of combinations. The returned comparator allows to compare
+ * any two combinations that can be produced by this instance's {@link #iterator() iterator}.
+ * Its {@code compare(int[],int[])} method will throw exceptions if passed combinations that are
+ * inconsistent with this instance:
+ *
+ * <ul>
+ * <li>{@code DimensionMismatchException} if the array lengths are not equal to {@code k},
+ * <li>{@code OutOfRangeException} if an element of the array is not within the interval [0,
+ * {@code n}).
+ * </ul>
+ *
+ * @return a lexicographic comparator.
+ */
+ public Comparator<int[]> comparator() {
+ return new LexicographicComparator(n, k);
+ }
+
+ /**
+ * Lexicographic combinations iterator.
+ *
+ * <p>Implementation follows Algorithm T in <i>The Art of Computer Programming</i> Internet
+ * Draft (PRE-FASCICLE 3A), "A Draft of Section 7.2.1.3 Generating All Combinations</a>, D.
+ * Knuth, 2004.
+ *
+ * <p>The degenerate cases {@code k == 0} and {@code k == n} are NOT handled by this
+ * implementation. If constructor arguments satisfy {@code k == 0} or {@code k >= n}, no
+ * exception is generated, but the iterator is empty.
+ */
+ private static class LexicographicIterator implements Iterator<int[]> {
+ /** Size of subsets returned by the iterator */
+ private final int k;
+
+ /**
+ * c[1], ..., c[k] stores the next combination; c[k + 1], c[k + 2] are sentinels.
+ *
+ * <p>Note that c[0] is "wasted" but this makes it a little easier to follow the code.
+ */
+ private final int[] c;
+
+ /** Return value for {@link #hasNext()} */
+ private boolean more = true;
+
+ /** Marker: smallest index such that c[j + 1] > j */
+ private int j;
+
+ /**
+ * Construct a CombinationIterator to enumerate k-sets from n.
+ *
+ * <p>NOTE: If {@code k === 0} or {@code k >= n}, the Iterator will be empty (that is,
+ * {@link #hasNext()} will return {@code false} immediately.
+ *
+ * @param n size of the set from which subsets are enumerated
+ * @param k size of the subsets to enumerate
+ */
+ LexicographicIterator(int n, int k) {
+ this.k = k;
+ c = new int[k + 3];
+ if (k == 0 || k >= n) {
+ more = false;
+ return;
+ }
+ // Initialize c to start with lexicographically first k-set
+ for (int i = 1; i <= k; i++) {
+ c[i] = i - 1;
+ }
+ // Initialize sentinels
+ c[k + 1] = n;
+ c[k + 2] = 0;
+ j = k; // Set up invariant: j is smallest index such that c[j + 1] > j
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasNext() {
+ return more;
+ }
+
+ /** {@inheritDoc} */
+ public int[] next() {
+ if (!more) {
+ throw new NoSuchElementException();
+ }
+ // Copy return value (prepared by last activation)
+ final int[] ret = new int[k];
+ System.arraycopy(c, 1, ret, 0, k);
+
+ // Prepare next iteration
+ // T2 and T6 loop
+ int x = 0;
+ if (j > 0) {
+ x = j;
+ c[j] = x;
+ j--;
+ return ret;
+ }
+ // T3
+ if (c[1] + 1 < c[2]) {
+ c[1]++;
+ return ret;
+ } else {
+ j = 2;
+ }
+ // T4
+ boolean stepDone = false;
+ while (!stepDone) {
+ c[j - 1] = j - 2;
+ x = c[j] + 1;
+ if (x == c[j + 1]) {
+ j++;
+ } else {
+ stepDone = true;
+ }
+ }
+ // T5
+ if (j > k) {
+ more = false;
+ return ret;
+ }
+ // T6
+ c[j] = x;
+ j--;
+ return ret;
+ }
+
+ /** Not supported. */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Iterator with just one element to handle degenerate cases (full array, empty array) for
+ * combination iterator.
+ */
+ private static class SingletonIterator implements Iterator<int[]> {
+ /** Singleton array */
+ private final int[] singleton;
+
+ /** True on initialization, false after first call to next */
+ private boolean more = true;
+
+ /**
+ * Create a singleton iterator providing the given array.
+ *
+ * @param singleton array returned by the iterator
+ */
+ SingletonIterator(final int[] singleton) {
+ this.singleton = singleton;
+ }
+
+ /**
+ * @return True until next is called the first time, then false
+ */
+ public boolean hasNext() {
+ return more;
+ }
+
+ /**
+ * @return the singleton in first activation; throws NSEE thereafter
+ */
+ public int[] next() {
+ if (more) {
+ more = false;
+ return singleton;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ /** Not supported */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Defines the lexicographic ordering of combinations, using the {@link #lexNorm(int[])} method.
+ */
+ private static class LexicographicComparator implements Comparator<int[]>, Serializable {
+ /** Serializable version identifier. */
+ private static final long serialVersionUID = 20130906L;
+
+ /** Size of the set from which combinations are drawn. */
+ private final int n;
+
+ /** Number of elements in each combination. */
+ private final int k;
+
+ /**
+ * @param n Size of the set from which subsets are selected.
+ * @param k Size of the subsets to be enumerated.
+ */
+ LexicographicComparator(int n, int k) {
+ this.n = n;
+ this.k = k;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws DimensionMismatchException if the array lengths are not equal to {@code k}.
+ * @throws OutOfRangeException if an element of the array is not within the interval [0,
+ * {@code n}).
+ */
+ public int compare(int[] c1, int[] c2) {
+ if (c1.length != k) {
+ throw new DimensionMismatchException(c1.length, k);
+ }
+ if (c2.length != k) {
+ throw new DimensionMismatchException(c2.length, k);
+ }
+
+ // Method "lexNorm" works with ordered arrays.
+ final int[] c1s = MathArrays.copyOf(c1);
+ Arrays.sort(c1s);
+ final int[] c2s = MathArrays.copyOf(c2);
+ Arrays.sort(c2s);
+
+ final long v1 = lexNorm(c1s);
+ final long v2 = lexNorm(c2s);
+
+ if (v1 < v2) {
+ return -1;
+ } else if (v1 > v2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Computes the value (in base 10) represented by the digit (interpreted in base {@code n})
+ * in the input array in reverse order. For example if {@code c} is {@code {3, 2, 1}}, and
+ * {@code n} is 3, the method will return 18.
+ *
+ * @param c Input array.
+ * @return the lexicographic norm.
+ * @throws OutOfRangeException if an element of the array is not within the interval [0,
+ * {@code n}).
+ */
+ private long lexNorm(int[] c) {
+ long ret = 0;
+ for (int i = 0; i < c.length; i++) {
+ final int digit = c[i];
+ if (digit < 0 || digit >= n) {
+ throw new OutOfRangeException(digit, 0, n - 1);
+ }
+
+ ret += c[i] * ArithmeticUtils.pow(n, i);
+ }
+ return ret;
+ }
+ }
+}