Combinations.java
/*
* 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.numbers.combinatorics;
import java.io.Serializable;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Iterator;
import java.util.Comparator;
import org.apache.commons.numbers.core.ArithmeticUtils;
/**
* 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.
*/
public final 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;
/**
* @param n Size of the set from which subsets are selected.
* @param k Size of the subsets to be enumerated.
* @throws IllegalArgumentException if {@code n < 0}.
* @throws IllegalArgumentException if {@code k > n} or {@code k < 0}.
*/
private Combinations(int n,
int k) {
BinomialCoefficient.checkBinomial(n, k);
this.n = n;
this.k = k;
}
/**
* Create an instance.
*
* @param n Size of the set from which subsets are selected.
* @param k Size of the subsets to be enumerated.
* @throws IllegalArgumentException if {@code n < 0}.
* @throws IllegalArgumentException if {@code k > n} or {@code k < 0}.
* @return a new instance.
*/
public static Combinations of(int n,
int k) {
return new Combinations(n, k);
}
/**
* 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;
}
/**
* Creates an iterator 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]}
* </p>
* If {@code k == 0} an iterator containing an empty array is returned;
* if {@code k == n} an iterator containing [0, ..., n - 1] is returned.
*/
@Override
public Iterator<int[]> iterator() {
return k == 0 || k == n ?
new SingletonIterator(k) :
new LexicographicIterator(n, k);
}
/**
* Creates a comparator.
* When performing a comparison, if an element of the array is not
* within the interval [0, {@code n}), an {@code IllegalArgumentException}
* will be thrown.
*
* @return a 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, D. Knuth, 2004.</p>
* <p>
* The degenerate cases {@code k == 0} and {@code k == n} are NOT handled by this
* implementation. It is assumed that {@code n > k > 0}.
* </p>
*/
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.
* </p>
*/
private final int[] c;
/** Return value for {@link #hasNext()}. */
private boolean more = true;
/** Marker: smallest index such that {@code c[j + 1] > j}. */
private int j;
/**
* Construct a CombinationIterator to enumerate {@code k}-sets from a set
* of size {@code n}.
* <p>
* NOTE: It is assumed that {@code n > k > 0}.
* </p>
*
* @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];
// 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}
*/
@Override
public boolean hasNext() {
return more;
}
/**
* {@inheritDoc}
*/
@Override
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.
*/
@Override
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[]> {
/** Number of elements of the singleton array. */
private final int n;
/** True on initialization, false after first call to next. */
private boolean more = true;
/**
* Create a singleton iterator providing the given array.
*
* @param n Size of the singleton array returned by the iterator.
*/
SingletonIterator(final int n) {
this.n = n;
}
/**
* @return {@code true} until next is called the first time,
* then {@code false}.
**/
@Override
public boolean hasNext() {
return more;
}
/**
* @return the singleton at the first activation.
* @throws NoSuchElementException after the first activation.
*/
@Override
public int[] next() {
if (more) {
more = false;
// Create singleton.
final int[] s = new int[n];
for (int i = 0; i < n; i++) {
s[i] = i;
}
return s;
} else {
throw new NoSuchElementException();
}
}
/**
* Unsupported.
*
* @throws UnsupportedOperationException Remove is not supported.
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Defines a lexicographic ordering of the combinations.
*
* The comparison is based on the value (in base 10) represented
* by the digit (interpreted in base {@code n}) in the input array,
* in reverse order.
*/
private static class LexicographicComparator
implements Comparator<int[]>,
Serializable {
/** Serializable version identifier. */
private static final long serialVersionUID = 20170520L;
/** 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 IllegalArgumentException if the array lengths are not
* equal to {@code k}.
* @throws IllegalArgumentException if an element of the array is not
* within the interval [0, {@code n}).
*/
@Override
public int compare(int[] c1,
int[] c2) {
if (c1.length != k) {
throw new CombinatoricsException(CombinatoricsException.MISMATCH, k, c1.length);
}
if (c2.length != k) {
throw new CombinatoricsException(CombinatoricsException.MISMATCH, k, c2.length);
}
// Method "lexNorm" works with ordered arrays.
final int[] c1s = Arrays.copyOf(c1, k);
final int[] c2s = Arrays.copyOf(c2, k);
Arrays.sort(c1s);
Arrays.sort(c2s);
final long v1 = lexNorm(c1s);
final long v2 = lexNorm(c2s);
return Long.compare(v1, v2);
}
/**
* 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 IllegalArgumentException 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 CombinatoricsException(CombinatoricsException.OUT_OF_RANGE, digit, 0, n - 1);
}
ret += c[i] * ArithmeticUtils.pow((long) n, i);
}
return ret;
}
}
}