001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.numbers.combinatorics;
019
020import java.io.Serializable;
021import java.util.Arrays;
022import java.util.NoSuchElementException;
023import java.util.Iterator;
024import java.util.Comparator;
025
026import org.apache.commons.numbers.core.ArithmeticUtils;
027
028/**
029 * Utility to create <a href="http://en.wikipedia.org/wiki/Combination">
030 * combinations</a> {@code (n, k)} of {@code k} elements in a set of
031 * {@code n} elements.
032 */
033public final class Combinations implements Iterable<int[]> {
034    /** Size of the set from which combinations are drawn. */
035    private final int n;
036    /** Number of elements in each combination. */
037    private final int k;
038
039    /**
040     * @param n Size of the set from which subsets are selected.
041     * @param k Size of the subsets to be enumerated.
042     * @throws IllegalArgumentException if {@code n < 0}.
043     * @throws IllegalArgumentException if {@code k > n} or {@code k < 0}.
044     */
045    private Combinations(int n,
046                         int k) {
047        BinomialCoefficient.checkBinomial(n, k);
048        this.n = n;
049        this.k = k;
050    }
051
052    /**
053     * Create an instance.
054     *
055     * @param n Size of the set from which subsets are selected.
056     * @param k Size of the subsets to be enumerated.
057     * @throws IllegalArgumentException if {@code n < 0}.
058     * @throws IllegalArgumentException if {@code k > n} or {@code k < 0}.
059     * @return a new instance.
060     */
061    public static Combinations of(int n,
062                                  int k) {
063        return new Combinations(n, k);
064    }
065
066    /**
067     * Gets the size of the set from which combinations are drawn.
068     *
069     * @return the size of the universe.
070     */
071    public int getN() {
072        return n;
073    }
074
075    /**
076     * Gets the number of elements in each combination.
077     *
078     * @return the size of the subsets to be enumerated.
079     */
080    public int getK() {
081        return k;
082    }
083
084    /**
085     * Creates an iterator whose range is the k-element subsets of
086     * {0, ..., n - 1} represented as {@code int[]} arrays.
087     * <p>
088     * The iteration order is lexicographic: the arrays returned by the
089     * {@link #iterator() iterator} are sorted in descending order and
090     * they are visited in lexicographic order with significance from
091     * right to left.
092     * For example, {@code new Combinations(4, 2).iterator()} returns
093     * an iterator that will generate the following sequence of arrays
094     * on successive calls to
095     * {@code next()}:<br>
096     * {@code [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3]}
097     * </p>
098     * If {@code k == 0} an iterator containing an empty array is returned;
099     * if {@code k == n} an iterator containing [0, ..., n - 1] is returned.
100     */
101    @Override
102    public Iterator<int[]> iterator() {
103        return k == 0 || k == n ?
104            new SingletonIterator(k) :
105            new LexicographicIterator(n, k);
106    }
107
108    /**
109     * Creates a comparator.
110     * When performing a comparison, if an element of the array is not
111     * within the interval [0, {@code n}), an {@code IllegalArgumentException}
112     * will be thrown.
113     *
114     * @return a comparator.
115     */
116    public Comparator<int[]> comparator() {
117        return new LexicographicComparator(n, k);
118    }
119
120    /**
121     * Lexicographic combinations iterator.
122     * <p>
123     * Implementation follows Algorithm T in <i>The Art of Computer Programming</i>
124     * Internet Draft (PRE-FASCICLE 3A), "A Draft of Section 7.2.1.3 Generating All
125     * Combinations, D. Knuth, 2004.</p>
126     * <p>
127     * The degenerate cases {@code k == 0} and {@code k == n} are NOT handled by this
128     * implementation. It is assumed that {@code n > k > 0}.
129     * </p>
130     */
131    private static class LexicographicIterator implements Iterator<int[]> {
132        /** Size of subsets returned by the iterator. */
133        private final int k;
134
135        /**
136         * c[1], ..., c[k] stores the next combination; c[k + 1], c[k + 2] are
137         * sentinels.
138         * <p>
139         * Note that c[0] is "wasted" but this makes it a little easier to
140         * follow the code.
141         * </p>
142         */
143        private final int[] c;
144
145        /** Return value for {@link #hasNext()}. */
146        private boolean more = true;
147
148        /** Marker: smallest index such that {@code c[j + 1] > j}. */
149        private int j;
150
151        /**
152         * Construct a CombinationIterator to enumerate {@code k}-sets from a set
153         * of size {@code n}.
154         * <p>
155         * NOTE: It is assumed that {@code n > k > 0}.
156         * </p>
157         *
158         * @param n Size of the set from which subsets are enumerated.
159         * @param k Size of the subsets to enumerate.
160         */
161        LexicographicIterator(int n, int k) {
162            this.k = k;
163            c = new int[k + 3];
164            // Initialize c to start with lexicographically first k-set
165            for (int i = 1; i <= k; i++) {
166                c[i] = i - 1;
167            }
168            // Initialize sentinels
169            c[k + 1] = n;
170            c[k + 2] = 0;
171            j = k; // Set up invariant: j is smallest index such that c[j + 1] > j
172        }
173
174        /**
175         * {@inheritDoc}
176         */
177        @Override
178        public boolean hasNext() {
179            return more;
180        }
181
182        /**
183         * {@inheritDoc}
184         */
185        @Override
186        public int[] next() {
187            if (!more) {
188                throw new NoSuchElementException();
189            }
190            // Copy return value (prepared by last activation)
191            final int[] ret = new int[k];
192            System.arraycopy(c, 1, ret, 0, k);
193
194            // Prepare next iteration
195            // T2 and T6 loop
196            int x = 0;
197            if (j > 0) {
198                x = j;
199                c[j] = x;
200                j--;
201                return ret;
202            }
203            // T3
204            if (c[1] + 1 < c[2]) {
205                c[1]++;
206                return ret;
207            } else {
208                j = 2;
209            }
210            // T4
211            boolean stepDone = false;
212            while (!stepDone) {
213                c[j - 1] = j - 2;
214                x = c[j] + 1;
215                if (x == c[j + 1]) {
216                    j++;
217                } else {
218                    stepDone = true;
219                }
220            }
221            // T5
222            if (j > k) {
223                more = false;
224                return ret;
225            }
226            // T6
227            c[j] = x;
228            j--;
229            return ret;
230        }
231
232        /**
233         * Not supported.
234         */
235        @Override
236        public void remove() {
237            throw new UnsupportedOperationException();
238        }
239    }
240
241    /**
242     * Iterator with just one element to handle degenerate cases (full array,
243     * empty array) for combination iterator.
244     */
245    private static class SingletonIterator implements Iterator<int[]> {
246        /** Number of elements of the singleton array. */
247        private final int n;
248        /** True on initialization, false after first call to next. */
249        private boolean more = true;
250        /**
251         * Create a singleton iterator providing the given array.
252         *
253         * @param n Size of the singleton array returned by the iterator.
254         */
255        SingletonIterator(final int n) {
256            this.n = n;
257        }
258        /**
259         * @return {@code true} until next is called the first time,
260         * then {@code false}.
261         **/
262        @Override
263        public boolean hasNext() {
264            return more;
265        }
266        /**
267         * @return the singleton at the first activation.
268         * @throws NoSuchElementException after the first activation.
269         */
270        @Override
271        public int[] next() {
272            if (more) {
273                more = false;
274                // Create singleton.
275                final int[] s = new int[n];
276                for (int i = 0; i < n; i++) {
277                    s[i] = i;
278                }
279                return s;
280            } else {
281                throw new NoSuchElementException();
282            }
283        }
284        /**
285         * Unsupported.
286         *
287         * @throws UnsupportedOperationException Remove is not supported.
288         */
289        @Override
290        public void remove() {
291            throw new UnsupportedOperationException();
292        }
293    }
294
295    /**
296     * Defines a lexicographic ordering of the combinations.
297     *
298     * The comparison is based on the value (in base 10) represented
299     * by the digit (interpreted in base {@code n}) in the input array,
300     * in reverse order.
301     */
302    private static class LexicographicComparator
303        implements Comparator<int[]>,
304                   Serializable {
305        /** Serializable version identifier. */
306        private static final long serialVersionUID = 20170520L;
307        /** Size of the set from which combinations are drawn. */
308        private final int n;
309        /** Number of elements in each combination. */
310        private final int k;
311
312        /**
313         * @param n Size of the set from which subsets are selected.
314         * @param k Size of the subsets to be enumerated.
315         */
316        LexicographicComparator(int n,
317                                int k) {
318            this.n = n;
319            this.k = k;
320        }
321
322        /**
323         * {@inheritDoc}
324         *
325         * @throws IllegalArgumentException if the array lengths are not
326         * equal to {@code k}.
327         * @throws IllegalArgumentException if an element of the array is not
328         * within the interval [0, {@code n}).
329         */
330        @Override
331        public int compare(int[] c1,
332                           int[] c2) {
333            if (c1.length != k) {
334                throw new CombinatoricsException(CombinatoricsException.MISMATCH, k, c1.length);
335            }
336            if (c2.length != k) {
337                throw new CombinatoricsException(CombinatoricsException.MISMATCH, k, c2.length);
338            }
339
340            // Method "lexNorm" works with ordered arrays.
341            final int[] c1s = Arrays.copyOf(c1, k);
342            final int[] c2s = Arrays.copyOf(c2, k);
343            Arrays.sort(c1s);
344            Arrays.sort(c2s);
345
346            final long v1 = lexNorm(c1s);
347            final long v2 = lexNorm(c2s);
348
349            return Long.compare(v1, v2);
350        }
351
352        /**
353         * Computes the value (in base 10) represented by the digit
354         * (interpreted in base {@code n}) in the input array in reverse
355         * order.
356         * For example if {@code c} is {@code {3, 2, 1}}, and {@code n}
357         * is 3, the method will return 18.
358         *
359         * @param c Input array.
360         * @return the lexicographic norm.
361         * @throws IllegalArgumentException if an element of the array is not
362         * within the interval [0, {@code n}).
363         */
364        private long lexNorm(int[] c) {
365            long ret = 0;
366            for (int i = 0; i < c.length; i++) {
367                final int digit = c[i];
368                if (digit < 0 ||
369                    digit >= n) {
370                    throw new CombinatoricsException(CombinatoricsException.OUT_OF_RANGE, digit, 0, n - 1);
371                }
372
373                ret += c[i] * ArithmeticUtils.pow((long) n, i);
374            }
375            return ret;
376        }
377    }
378}