001// ============================================================================
002//   Copyright 2006-2012 Daniel W. Dyer
003//
004//   Licensed under the Apache License, Version 2.0 (the "License");
005//   you may not use this file except in compliance with the License.
006//   You may obtain a copy of the License at
007//
008//       http://www.apache.org/licenses/LICENSE-2.0
009//
010//   Unless required by applicable law or agreed to in writing, software
011//   distributed under the License is distributed on an "AS IS" BASIS,
012//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013//   See the License for the specific language governing permissions and
014//   limitations under the License.
015// ============================================================================
016package squidpony.squidmath;
017
018import java.io.Serializable;
019import java.math.BigInteger;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.List;
024
025/**
026 * Combination generator for generating all combinations of a given size from
027 * the specified set of elements.  For performance reasons, this implementation
028 * is restricted to operating with set sizes and combination lengths that produce
029 * no more than 2^63 different combinations.
030 * <br>
031 * Originally part of the <a href="http://maths.uncommons.org/">Uncommon Maths software package</a>.
032 * @param <T> The type of element that the combinations are made from.
033 * @author Daniel Dyer (modified from the original version written by Michael
034 * Gilleland of Merriam Park Software -
035 * <a href="http://www.merriampark.com/perm.htm">http://www.merriampark.com/comb.htm</a>).
036 * @see PermutationGenerator
037 */
038public class CombinationGenerator<T> implements Iterable<List<T>>, Serializable
039{
040    private static final long serialVersionUID = 5998145341506278361L;
041
042    private final T[] elements;
043    private final int[] combinationIndices;
044    private long remainingCombinations;
045    private long totalCombinations;
046
047    private CombinationGenerator()
048    {
049        elements = null;
050        combinationIndices = null;
051    };
052    /**
053     * Create a combination generator that generates all combinations of
054     * a specified length from the given set.
055     * @param elements The set from which to generate combinations; will be used directly (not copied)
056     * @param combinationLength The length of the combinations to be generated.
057     */
058    public CombinationGenerator(T[] elements,
059                                int combinationLength)
060    {
061        if (combinationLength > elements.length)
062        {
063            throw new IllegalArgumentException("Combination length cannot be greater than set size.");
064        }
065
066        this.elements = elements;
067        this.combinationIndices = new int[combinationLength];
068
069        BigInteger sizeFactorial = MathExtras.bigFactorial(elements.length);
070        BigInteger lengthFactorial = MathExtras.bigFactorial(combinationLength);
071        BigInteger differenceFactorial = MathExtras.bigFactorial(elements.length - combinationLength);
072        BigInteger total = sizeFactorial.divide(differenceFactorial.multiply(lengthFactorial));
073        
074        if (total.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0)
075        {
076            throw new IllegalArgumentException("Total number of combinations must not be more than 2^63."); 
077        }
078
079        totalCombinations = total.longValue();
080        reset();
081    }
082
083
084    /**
085     * Create a combination generator that generates all combinations of
086     * a specified length from the given set.
087     * @param elements The set from which to generate combinations.
088     * @param combinationLength The length of the combinations to be generated.
089     * @param filler An array of T with the same length as elements or less (often 0);
090     *               needed because GWT can't create a generic array. If elements is not
091     *               a Collection defined in the JDK (by GWT), then this should have
092     *               exactly the same length as elements, since GWT can create larger
093     *               versions of an array on its own, but our code can't easily.
094     */
095    public CombinationGenerator(Collection<T> elements,
096                                int combinationLength,
097                                T[] filler)
098    {
099        this(elements.toArray(filler), combinationLength);
100    }
101
102
103    /**
104     * Reset the combination generator.
105     */
106    public final void reset()
107    {
108        for (int i = 0; i < combinationIndices.length; i++)
109        {
110            combinationIndices[i] = i;
111        }
112        remainingCombinations = totalCombinations;
113    }
114
115
116    /**
117     * @return The number of combinations not yet generated.
118     */
119    public long getRemainingCombinations()
120    {
121        return remainingCombinations;
122    }
123
124
125    /**
126     * Are there more combinations?
127     * @return true if there are more combinations available, false otherwise.
128     */
129    public boolean hasMore()
130    {
131        return remainingCombinations > 0;
132    }
133
134
135    /**
136     * @return The total number of combinations.
137     */
138    public long getTotalCombinations()
139    {
140        return totalCombinations;
141    }
142
143
144    /**
145     * Generate the next combination and return an array containing
146     * the appropriate elements.  This overloaded method allows the caller
147     * to provide an array that will be used and returned.
148     * The purpose of this is to improve performance when iterating over
149     * combinations.  This method allows a single array instance to be reused.
150     * @param destination Provides an array to use to create the
151     * combination.  The specified array must be the same length as a
152     * combination.
153     * @return The provided array now containing the elements of the combination.
154     */
155    public T[] nextCombinationAsArray(T[] destination)
156    {
157        if (destination.length != combinationIndices.length)
158        {
159            throw new IllegalArgumentException("Destination array must be the same length as combinations.");
160        }
161        generateNextCombinationIndices();
162        for (int i = 0; i < combinationIndices.length; i++)
163        {
164            destination[i] = elements[combinationIndices[i]];
165        }
166        return destination;
167    }
168
169
170    /**
171     * Generate the next combination and return a list containing the
172     * appropriate elements.
173     * @see #nextCombinationAsList(List)
174     * @return A list containing the elements that make up the next combination.
175     */
176    public List<T> nextCombinationAsList()
177    {
178        return nextCombinationAsList(new ArrayList<T>(elements.length));
179    }
180
181
182    /**
183     * Generate the next combination and return a list containing
184     * the appropriate elements.  This overloaded method allows the caller
185     * to provide a list that will be used and returned.
186     * The purpose of this is to improve performance when iterating over
187     * combinations.  If the {@link #nextCombinationAsList()} method is
188     * used it will create a new list every time.  When iterating over
189     * combinations this will result in lots of short-lived objects that
190     * have to be garbage collected.  This method allows a single list
191     * instance to be reused in such circumstances.
192     * @param destination Provides a list to use to create the
193     * combination.
194     * @return The provided list now containing the elements of the combination.
195     */
196    public List<T> nextCombinationAsList(List<T> destination)
197    {
198        generateNextCombinationIndices();
199        // Generate actual combination.
200        destination.clear();
201        for (int i : combinationIndices)
202        {
203            destination.add(elements[i]);
204        }
205        return destination;
206    }
207
208
209
210    /**
211     * Generate the indices into the elements array for the next combination. The
212     * algorithm is from Kenneth H. Rosen, Discrete Mathematics and Its Applications,
213     * 2nd edition (NY: McGraw-Hill, 1991), p. 286.
214     */
215    private void generateNextCombinationIndices()
216    {
217        if (remainingCombinations == 0)
218        {
219            throw new IllegalStateException("There are no combinations remaining.  " +
220                                            "Generator must have reset() called to continue.");
221        }
222        else if (remainingCombinations < totalCombinations)
223        {
224            int i = combinationIndices.length - 1;
225            while (combinationIndices[i] == elements.length - combinationIndices.length + i)
226            {
227                i--;
228            }
229            ++combinationIndices[i];
230            for (int j = i + 1; j < combinationIndices.length; j++)
231            {
232                combinationIndices[j] = combinationIndices[i] + j - i;
233            }
234        }
235        --remainingCombinations;
236    }
237
238
239    /**
240     * <p>Provides a read-only iterator for iterating over the combinations
241     * generated by this object.  This method is the implementation of the
242     * {@link Iterable} interface that permits instances of this class to be
243     * used with the new-style for loop.</p>
244     * <p>For example:</p>
245     * <pre>
246     * List&lt;Integer&gt; elements = Arrays.asList(1, 2, 3);
247     * CombinationGenerator&lt;Integer&gt; combinations = new CombinationGenerator(elements, 2);
248     * for (List&lt;Integer&gt; c : combinations)
249     * {
250     *     // Do something with each combination.
251     * }
252     * </pre>
253     * @return An iterator.
254     * @since 1.1
255     */
256    public Iterator<List<T>> iterator()
257    {
258        return new Iterator<List<T>>()
259        {
260            public boolean hasNext()
261            {
262                return hasMore();
263            }
264
265
266            public List<T> next()
267            {
268                return nextCombinationAsList();
269            }
270
271
272            public void remove()
273            {
274                throw new UnsupportedOperationException("Iterator does not support removal.");
275            }
276        };
277    }
278
279}