Author: erans
Date: Fri Sep  6 15:32:37 2013
New Revision: 1520605

URL: http://svn.apache.org/r1520605
Log:
MATH-1027
Added accessors and (lexicographic) comparator.

Modified:
    
commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Combinations.java
    
commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/CombinationsTest.java

Modified: 
commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Combinations.java
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Combinations.java?rev=1520605&r1=1520604&r2=1520605&view=diff
==============================================================================
--- 
commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Combinations.java
 (original)
+++ 
commons/proper/math/trunk/src/main/java/org/apache/commons/math3/util/Combinations.java
 Fri Sep  6 15:32:37 2013
@@ -17,8 +17,13 @@
 package org.apache.commons.math3.util;
 
 import java.util.Iterator;
+import java.util.Comparator;
+import java.util.Arrays;
 import java.util.NoSuchElementException;
+import java.io.Serializable;
 import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.OutOfRangeException;
 
 /**
  * Utility to create <a href="http://en.wikipedia.org/wiki/Combination";>
@@ -96,6 +101,24 @@ public class Combinations implements Ite
         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<int[]> iterator() {
@@ -120,6 +143,24 @@ public class Combinations implements Ite
     }
 
     /**
+     * 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:
+     * <ul>
+     *  <li>{@code DimensionMismatchException} if the array lengths are not
+     *      equal to {@code k},</li>
+     *  <li>{@code OutOfRangeException} if an element of the array is not
+     *      within the interval [0, {@code n}).</li>
+     * </ul>
+     * @return a lexicographic comparator.
+     */
+    public Comparator<int[]> comparator() {
+        return new LexicographicComparator(n, k);
+    }
+
+    /**
      * Lexicographic combinations iterator.
      * <p>
      * Implementation follows Algorithm T in <i>The Art of Computer 
Programming</i>
@@ -278,4 +319,89 @@ public class Combinations implements Ite
             throw new UnsupportedOperationException();
         }
     }
+
+    /**
+     * Defines the lexicographic ordering of combinations, using
+     * the {@link #lexNorm(int[],int)} method.
+     */
+    private static class LexicographicComparator
+        implements Comparator<int[]>, 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.
+         */
+        public LexicographicComparator(int n,
+                                       int k) {
+            this.n = n;
+            this.k = k;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * @throws DimensionMismatchException if the array lengths are not
+         * equal to {@code k}.
+         * @throws OutOfRangeException if an element of the array is not
+         * within the interval [0, {@code n}).
+         */
+        public int compare(int[] c1,
+                           int[] c2) {
+            if (c1.length != k) {
+                throw new DimensionMismatchException(c1.length, k);
+            }
+            if (c2.length != k) {
+                throw new DimensionMismatchException(c2.length, k);
+            }
+
+            // Method "lexNorm" works with ordered arrays.
+            final int[] c1s = MathArrays.copyOf(c1);
+            Arrays.sort(c1s);
+            final int[] c2s = MathArrays.copyOf(c2);
+            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 OutOfRangeException 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];
+                if (digit < 0 ||
+                    digit >= n) {
+                    throw new OutOfRangeException(digit, 0, n - 1);
+                }
+
+                ret += c[i] * ArithmeticUtils.pow(n, (long) i);
+            }
+            return ret;
+        }
+    }
 }

Modified: 
commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/CombinationsTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/CombinationsTest.java?rev=1520605&r1=1520604&r2=1520605&view=diff
==============================================================================
--- 
commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/CombinationsTest.java
 (original)
+++ 
commons/proper/math/trunk/src/test/java/org/apache/commons/math3/util/CombinationsTest.java
 Fri Sep  6 15:32:37 2013
@@ -17,6 +17,9 @@
 package org.apache.commons.math3.util;
 
 import java.util.Iterator;
+import java.util.Comparator;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.OutOfRangeException;
 import org.apache.commons.math3.exception.MathIllegalArgumentException;
 import org.junit.Assert;
 import org.junit.Test;
@@ -26,19 +29,93 @@ import org.junit.Test;
  *
  * @version $Id$
  */
-public class CombinationsTest {    
+public class CombinationsTest {
+    @Test
+    public void testAccessor1() {
+        final int n = 5;
+        final int k = 3;
+        Assert.assertEquals(n, new Combinations(n, k).getN());
+    }
+    @Test
+    public void testAccessor2() {
+        final int n = 5;
+        final int k = 3;
+        Assert.assertEquals(k, new Combinations(n, k).getK());
+    }
+
     @Test
     public void testLexicographicIterator() {
-        checkLexicographicIterator(new Combinations(5, 3), 5, 3);
-        checkLexicographicIterator(new Combinations(6, 4), 6, 4);
-        checkLexicographicIterator(new Combinations(8, 2), 8, 2);
-        checkLexicographicIterator(new Combinations(6, 1), 6, 1);
-        checkLexicographicIterator(new Combinations(3, 3), 3, 3);
-        checkLexicographicIterator(new Combinations(1, 1), 1, 1);
-        checkLexicographicIterator(new Combinations(1, 0), 1, 0);
-        checkLexicographicIterator(new Combinations(0, 0), 0, 0);
-        checkLexicographicIterator(new Combinations(4, 2), 4, 2);
-        checkLexicographicIterator(new Combinations(123, 2), 123, 2);
+        checkLexicographicIterator(new Combinations(5, 3));
+        checkLexicographicIterator(new Combinations(6, 4));
+        checkLexicographicIterator(new Combinations(8, 2));
+        checkLexicographicIterator(new Combinations(6, 1));
+        checkLexicographicIterator(new Combinations(3, 3));
+        checkLexicographicIterator(new Combinations(1, 1));
+        checkLexicographicIterator(new Combinations(1, 0));
+        checkLexicographicIterator(new Combinations(0, 0));
+        checkLexicographicIterator(new Combinations(4, 2));
+        checkLexicographicIterator(new Combinations(123, 2));
+    }
+
+    @Test(expected=DimensionMismatchException.class)
+    public void testLexicographicComparatorWrongIterate1() {
+        final int n = 5;
+        final int k = 3;
+        final Comparator<int[]> comp = new Combinations(n, k).comparator();
+        comp.compare(new int[] {1}, new int[] {0, 1, 2});
+    }
+
+    @Test(expected=DimensionMismatchException.class)
+    public void testLexicographicComparatorWrongIterate2() {
+        final int n = 5;
+        final int k = 3;
+        final Comparator<int[]> comp = new Combinations(n, k).comparator();
+        comp.compare(new int[] {0, 1, 2}, new int[] {0, 1, 2, 3});
+    }
+
+    @Test(expected=OutOfRangeException.class)
+    public void testLexicographicComparatorWrongIterate3() {
+        final int n = 5;
+        final int k = 3;
+        final Comparator<int[]> comp = new Combinations(n, k).comparator();
+        comp.compare(new int[] {1, 2, 5}, new int[] {0, 1, 2});
+    }
+
+    @Test(expected=OutOfRangeException.class)
+    public void testLexicographicComparatorWrongIterate4() {
+        final int n = 5;
+        final int k = 3;
+        final Comparator<int[]> comp = new Combinations(n, k).comparator();
+        comp.compare(new int[] {1, 2, 4}, new int[] {-1, 1, 2});
+    }
+
+    @Test
+    public void testLexicographicComparator() {
+        final int n = 5;
+        final int k = 3;
+        final Comparator<int[]> comp = new Combinations(n, k).comparator();
+        Assert.assertEquals(1, comp.compare(new int[] {1, 2, 4},
+                                            new int[] {1, 2, 3}));
+        Assert.assertEquals(-1, comp.compare(new int[] {0, 1, 4},
+                                             new int[] {0, 2, 4}));
+        Assert.assertEquals(0, comp.compare(new int[] {1, 3, 4},
+                                            new int[] {1, 3, 4}));
+    }
+
+    /**
+     * Check that iterates can be passed unsorted.
+     */
+    @Test
+    public void testLexicographicComparatorUnsorted() {
+        final int n = 5;
+        final int k = 3;
+        final Comparator<int[]> comp = new Combinations(n, k).comparator();
+        Assert.assertEquals(1, comp.compare(new int[] {1, 4, 2},
+                                            new int[] {1, 3, 2}));
+        Assert.assertEquals(-1, comp.compare(new int[] {0, 4, 1},
+                                             new int[] {0, 4, 2}));
+        Assert.assertEquals(0, comp.compare(new int[] {1, 4, 3},
+                                            new int[] {1, 3, 4}));
     }
 
     @Test
@@ -71,25 +148,35 @@ public class CombinationsTest {    
      * and each array itself increasing.
      * 
      * @param c Combinations.
-     * @param n Size of universe.
-     * @param k Size of subsets.
      */
-    private void checkLexicographicIterator(Iterable<int[]> c,
-                                            int n,
-                                            int k) {
-        long lastLex = -1;
-        long length = 0;
+    private void checkLexicographicIterator(Combinations c) {
+        final Comparator<int[]> comp = c.comparator();
+        final int n = c.getN();
+        final int k = c.getK();
+
+        int[] lastIterate = null;
+
+        long numIterates = 0;
         for (int[] iterate : c) {
             Assert.assertEquals(k, iterate.length);
-            final long curLex = lexNorm(iterate, n);
-            Assert.assertTrue(curLex > lastLex);
-            lastLex = curLex;
-            length++;
+
+            // Check that the sequence of iterates is ordered.
+            if (lastIterate != null) {
+                Assert.assertTrue(comp.compare(iterate, lastIterate) == 1);
+            }
+
+            // Check that each iterate is ordered.
             for (int i = 1; i < iterate.length; i++) {
                 Assert.assertTrue(iterate[i] > iterate[i - 1]);
             }
+
+            lastIterate = iterate;
+            ++numIterates;
         }
-        Assert.assertEquals(CombinatoricsUtils.binomialCoefficient(n, k), 
length);
+
+        // Check the number of iterates.
+        Assert.assertEquals(CombinatoricsUtils.binomialCoefficient(n, k),
+                            numIterates);
     }
     
     @Test
@@ -108,20 +195,4 @@ public class CombinationsTest {    
             // ignored
         }
     }
-    
-    /**
-     * Returns the value represented by the digits in the input array in 
reverse order.
-     * For example [3,2,1] returns 123.
-     * 
-     * @param iterate input array
-     * @param n size of universe
-     * @return lexicographic norm
-     */
-    private long lexNorm(int[] iterate, int n) {
-        long ret = 0;
-        for (int i = iterate.length - 1; i >= 0; i--) {
-            ret += iterate[i] * ArithmeticUtils.pow(n, (long) i);
-        }
-        return ret;
-    }
 }


Reply via email to