org.uncommons.maths.combinatorics.CombinationGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of uncommons-maths Show documentation
Show all versions of uncommons-maths Show documentation
Random number generators, probability distributions, combinatorics and statistics for Java.
The newest version!
// ============================================================================
// Copyright 2006-2010 Daniel W. Dyer
//
// Licensed 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.uncommons.maths.combinatorics;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.uncommons.maths.Maths;
/**
* Combination generator for generating all combinations of a given size from
* the specified set of elements. For performance reasons, this implementation
* is restricted to operating with set sizes and combination lengths that produce
* no more than 2^63 different combinations.
* @param The type of element that the combinations are made from.
* @author Daniel Dyer (modified from the original version written by Michael
* Gilleland of Merriam Park Software -
* http://www.merriampark.com/comb.htm).
* @see PermutationGenerator
*/
public class CombinationGenerator implements Iterable>
{
private final T[] elements;
private final int[] combinationIndices;
private long remainingCombinations;
private long totalCombinations;
/**
* Create a combination generator that generates all combinations of
* a specified length from the given set.
* @param elements The set from which to generate combinations.
* @param combinationLength The length of the combinations to be generated.
*/
public CombinationGenerator(T[] elements,
int combinationLength)
{
if (combinationLength > elements.length)
{
throw new IllegalArgumentException("Combination length cannot be greater than set size.");
}
this.elements = elements.clone();
this.combinationIndices = new int[combinationLength];
BigInteger sizeFactorial = Maths.bigFactorial(elements.length);
BigInteger lengthFactorial = Maths.bigFactorial(combinationLength);
BigInteger differenceFactorial = Maths.bigFactorial(elements.length - combinationLength);
BigInteger total = sizeFactorial.divide(differenceFactorial.multiply(lengthFactorial));
if (total.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0)
{
throw new IllegalArgumentException("Total number of combinations must not be more than 2^63.");
}
totalCombinations = total.longValue();
reset();
}
/**
* Create a combination generator that generates all combinations of
* a specified length from the given set.
* @param elements The set from which to generate combinations.
* @param combinationLength The length of the combinations to be generated.
*/
@SuppressWarnings("unchecked")
public CombinationGenerator(Collection elements,
int combinationLength)
{
this(elements.toArray((T[]) new Object[elements.size()]),
combinationLength);
}
/**
* Reset the combination generator.
*/
public final void reset()
{
for (int i = 0; i < combinationIndices.length; i++)
{
combinationIndices[i] = i;
}
remainingCombinations = totalCombinations;
}
/**
* @return The number of combinations not yet generated.
*/
public long getRemainingCombinations()
{
return remainingCombinations;
}
/**
* Are there more combinations?
* @return true if there are more combinations available, false otherwise.
*/
public boolean hasMore()
{
return remainingCombinations > 0;
}
/**
* @return The total number of combinations.
*/
public long getTotalCombinations()
{
return totalCombinations;
}
/**
* Generate the next combination and return an array containing
* the appropriate elements.
* @see #nextCombinationAsArray(Object[])
* @see #nextCombinationAsList()
* @return An array containing the elements that make up the next combination.
*/
@SuppressWarnings("unchecked")
public T[] nextCombinationAsArray()
{
T[] combination = (T[]) Array.newInstance(elements.getClass().getComponentType(),
combinationIndices.length);
return nextCombinationAsArray(combination);
}
/**
* Generate the next combination and return an array containing
* the appropriate elements. This overloaded method allows the caller
* to provide an array that will be used and returned.
* The purpose of this is to improve performance when iterating over
* combinations. If the {@link #nextCombinationAsArray()} method is
* used it will create a new array every time. When iterating over
* combinations this will result in lots of short-lived objects that
* have to be garbage collected. This method allows a single array
* instance to be reused in such circumstances.
* @param destination Provides an array to use to create the
* combination. The specified array must be the same length as a
* combination.
* @return The provided array now containing the elements of the combination.
*/
public T[] nextCombinationAsArray(T[] destination)
{
if (destination.length != combinationIndices.length)
{
throw new IllegalArgumentException("Destination array must be the same length as combinations.");
}
generateNextCombinationIndices();
for (int i = 0; i < combinationIndices.length; i++)
{
destination[i] = elements[combinationIndices[i]];
}
return destination;
}
/**
* Generate the next combination and return a list containing the
* appropriate elements.
* @see #nextCombinationAsList(List)
* @see #nextCombinationAsArray()
* @return A list containing the elements that make up the next combination.
*/
public List nextCombinationAsList()
{
return nextCombinationAsList(new ArrayList(elements.length));
}
/**
* Generate the next combination and return a list containing
* the appropriate elements. This overloaded method allows the caller
* to provide a list that will be used and returned.
* The purpose of this is to improve performance when iterating over
* combinations. If the {@link #nextCombinationAsList()} method is
* used it will create a new list every time. When iterating over
* combinations this will result in lots of short-lived objects that
* have to be garbage collected. This method allows a single list
* instance to be reused in such circumstances.
* @param destination Provides a list to use to create the
* combination.
* @return The provided list now containing the elements of the combination.
*/
public List nextCombinationAsList(List destination)
{
generateNextCombinationIndices();
// Generate actual combination.
destination.clear();
for (int i : combinationIndices)
{
destination.add(elements[i]);
}
return destination;
}
/**
* Generate the indices into the elements array for the next combination. The
* algorithm is from Kenneth H. Rosen, Discrete Mathematics and Its Applications,
* 2nd edition (NY: McGraw-Hill, 1991), p. 286.
*/
private void generateNextCombinationIndices()
{
if (remainingCombinations == 0)
{
throw new IllegalStateException("There are no combinations remaining. " +
"Generator must be reset to continue using.");
}
else if (remainingCombinations < totalCombinations)
{
int i = combinationIndices.length - 1;
while (combinationIndices[i] == elements.length - combinationIndices.length + i)
{
i--;
}
++combinationIndices[i];
for (int j = i + 1; j < combinationIndices.length; j++)
{
combinationIndices[j] = combinationIndices[i] + j - i;
}
}
--remainingCombinations;
}
/**
* Provides a read-only iterator for iterating over the combinations
* generated by this object. This method is the implementation of the
* {@link Iterable} interface that permits instances of this class to be
* used with the new-style for loop.
* For example:
*
* List<Integer> elements = Arrays.asList(1, 2, 3);
* CombinationGenerator<Integer> combinations = new CombinationGenerator(elements, 2);
* for (List<Integer> c : combinations)
* {
* // Do something with each combination.
* }
*
* @return An iterator.
* @since 1.1
*/
public Iterator> iterator()
{
return new Iterator>()
{
public boolean hasNext()
{
return hasMore();
}
public List next()
{
return nextCombinationAsList();
}
public void remove()
{
throw new UnsupportedOperationException("Iterator does not support removal.");
}
};
}
}