org.hipparchus.util.Combinations Maven / Gradle / Ivy
/*
* 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.
*/
/*
* This is not the original file distributed by the Apache Software Foundation
* It has been modified by the Hipparchus project
*/
package org.hipparchus.util;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.hipparchus.exception.MathRuntimeException;
/**
* Utility to create combinations {@code (n, k)} of {@code k} elements
* in a set of {@code n} elements.
*
* @see
* Combination @ Wikipedia
*/
public class Combinations implements Iterable {
/** 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.
*
* 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()}:
* {@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.hipparchus.exception.MathIllegalArgumentException if {@code n < 0}.
* @throws org.hipparchus.exception.MathIllegalArgumentException 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.
*
* 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()}:
* {@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.hipparchus.exception.MathIllegalArgumentException if {@code n < 0}.
* @throws org.hipparchus.exception.MathIllegalArgumentException 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} */
@Override
public Iterator iterator() {
if (k == 0 ||
k == n) {
return new SingletonIterator(MathArrays.natural(k));
}
switch (iterationOrder) {
case LEXICOGRAPHIC:
return new LexicographicIterator(n, k);
default:
throw MathRuntimeException.createInternalError(); // 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:
*
* - if the array lengths are not equal to {@code k},
* - if an element of the array is not within the interval [0, {@code n}).
*
* @return a lexicographic comparator.
*/
public Comparator comparator() {
return new LexicographicComparator(n, k);
}
/**
* Lexicographic combinations iterator.
*
* Implementation follows Algorithm T in The Art of Computer Programming
* Internet Draft (PRE-FASCICLE 3A), "A Draft of Section 7.2.1.3 Generating All
* Combinations, D. Knuth, 2004.
*
* 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 {
/** 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.
*
* 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.
*
* 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}
*/
@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 {
/** 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 */
@Override
public boolean hasNext() {
return more;
}
/** @return the singleton in first activation; throws NSEE thereafter */
@Override
public int[] next() {
if (more) {
more = false;
return singleton;
} else {
throw new NoSuchElementException();
}
}
/** Not supported */
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Defines the lexicographic ordering of combinations, using
* the {@link #lexNorm(int[])} method.
*/
private static class LexicographicComparator
implements Comparator, 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 org.hipparchus.exception.MathIllegalArgumentException
* if the array lengths are not equal to {@code k}.
* @throws org.hipparchus.exception.MathIllegalArgumentException
* if an element of the array is not within the interval [0, {@code n}).
*/
@Override
public int compare(int[] c1, int[] c2) {
MathUtils.checkDimension(c1.length, k);
MathUtils.checkDimension(c2.length, k);
// Method "lexNorm" works with ordered arrays.
final int[] c1s = c1.clone();
Arrays.sort(c1s);
final int[] c2s = c2.clone();
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 org.hipparchus.exception.MathIllegalArgumentException
* 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];
MathUtils.checkRangeInclusive(digit, 0, n - 1);
ret += c[i] * ArithmeticUtils.pow(n, i);
}
return ret;
}
}
}