All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.uncommons.maths.combinatorics.CombinationGenerator Maven / Gradle / Ivy

Go to download

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."); } }; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy