View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.util;
23  
24  import org.hipparchus.exception.MathIllegalArgumentException;
25  import org.junit.jupiter.api.Test;
26  
27  import java.lang.reflect.Constructor;
28  import java.lang.reflect.InvocationTargetException;
29  import java.lang.reflect.Method;
30  import java.util.Comparator;
31  import java.util.Iterator;
32  import java.util.NoSuchElementException;
33  
34  import static org.junit.jupiter.api.Assertions.assertEquals;
35  import static org.junit.jupiter.api.Assertions.assertFalse;
36  import static org.junit.jupiter.api.Assertions.assertThrows;
37  import static org.junit.jupiter.api.Assertions.assertTrue;
38  import static org.junit.jupiter.api.Assertions.fail;
39  
40  /**
41   * Tests for the {@link Combinations} class.
42   */
43  class CombinationsTest {
44      @Test
45      void testAccessor1() {
46          final int n = 5;
47          final int k = 3;
48          assertEquals(n, new Combinations(n, k).getN());
49      }
50  
51      @Test
52      void testAccessor2() {
53          final int n = 5;
54          final int k = 3;
55          assertEquals(k, new Combinations(n, k).getK());
56      }
57  
58      @Test
59      void testLexicographicIterator() {
60          checkLexicographicIterator(new Combinations(5, 3));
61          checkLexicographicIterator(new Combinations(6, 4));
62          checkLexicographicIterator(new Combinations(8, 2));
63          checkLexicographicIterator(new Combinations(6, 1));
64          checkLexicographicIterator(new Combinations(3, 3));
65          checkLexicographicIterator(new Combinations(1, 1));
66          checkLexicographicIterator(new Combinations(1, 0));
67          checkLexicographicIterator(new Combinations(0, 0));
68          checkLexicographicIterator(new Combinations(4, 2));
69          checkLexicographicIterator(new Combinations(123, 2));
70      }
71  
72      @Test
73      void testLexicographicComparatorWrongIterate1() {
74          assertThrows(MathIllegalArgumentException.class, () -> {
75              final int n = 5;
76              final int k = 3;
77              final Comparator<int[]> comp = new Combinations(n, k).comparator();
78              comp.compare(new int[]{1}, new int[]{0, 1, 2});
79          });
80      }
81  
82      @Test
83      void testLexicographicComparatorWrongIterate2() {
84          assertThrows(MathIllegalArgumentException.class, () -> {
85              final int n = 5;
86              final int k = 3;
87              final Comparator<int[]> comp = new Combinations(n, k).comparator();
88              comp.compare(new int[]{0, 1, 2}, new int[]{0, 1, 2, 3});
89          });
90      }
91  
92      @Test
93      void testLexicographicComparatorWrongIterate3() {
94          assertThrows(MathIllegalArgumentException.class, () -> {
95              final int n = 5;
96              final int k = 3;
97              final Comparator<int[]> comp = new Combinations(n, k).comparator();
98              comp.compare(new int[]{1, 2, 5}, new int[]{0, 1, 2});
99          });
100     }
101 
102     @Test
103     void testLexicographicComparatorWrongIterate4() {
104         assertThrows(MathIllegalArgumentException.class, () -> {
105             final int n = 5;
106             final int k = 3;
107             final Comparator<int[]> comp = new Combinations(n, k).comparator();
108             comp.compare(new int[]{1, 2, 4}, new int[]{-1, 1, 2});
109         });
110     }
111 
112     @Test
113     void testLexicographicComparator() {
114         final int n = 5;
115         final int k = 3;
116         final Comparator<int[]> comp = new Combinations(n, k).comparator();
117         assertEquals(1, comp.compare(new int[] {1, 2, 4},
118                                             new int[] {1, 2, 3}));
119         assertEquals(-1, comp.compare(new int[] {0, 1, 4},
120                                              new int[] {0, 2, 4}));
121         assertEquals(0, comp.compare(new int[] {1, 3, 4},
122                                             new int[] {1, 3, 4}));
123     }
124 
125     /**
126      * Check that iterates can be passed unsorted.
127      */
128     @Test
129     void testLexicographicComparatorUnsorted() {
130         final int n = 5;
131         final int k = 3;
132         final Comparator<int[]> comp = new Combinations(n, k).comparator();
133         assertEquals(1, comp.compare(new int[] {1, 4, 2},
134                                             new int[] {1, 3, 2}));
135         assertEquals(-1, comp.compare(new int[] {0, 4, 1},
136                                              new int[] {0, 4, 2}));
137         assertEquals(0, comp.compare(new int[] {1, 4, 3},
138                                             new int[] {1, 3, 4}));
139     }
140 
141     @Test
142     void testEmptyCombination() {
143         final Iterator<int[]> iter = new Combinations(12345, 0).iterator();
144         assertTrue(iter.hasNext());
145         final int[] c = iter.next();
146         assertEquals(0, c.length);
147         assertFalse(iter.hasNext());
148     }
149 
150     @Test
151     void testFullSetCombination() {
152         final int n = 67;
153         final Iterator<int[]> iter = new Combinations(n, n).iterator();
154         assertTrue(iter.hasNext());
155         final int[] c = iter.next();
156         assertEquals(n, c.length);
157 
158         for (int i = 0; i < n; i++) {
159             assertEquals(i, c[i]);
160         }
161 
162         assertFalse(iter.hasNext());
163     }
164 
165     /**
166      * Verifies that the iterator generates a lexicographically
167      * increasing sequence of b(n,k) arrays, each having length k
168      * and each array itself increasing.
169      *
170      * @param c Combinations.
171      */
172     private void checkLexicographicIterator(Combinations c) {
173         final Comparator<int[]> comp = c.comparator();
174         final int n = c.getN();
175         final int k = c.getK();
176 
177         int[] lastIterate = null;
178 
179         long numIterates = 0;
180         for (int[] iterate : c) {
181             assertEquals(k, iterate.length);
182 
183             // Check that the sequence of iterates is ordered.
184             if (lastIterate != null) {
185                 assertEquals(1, comp.compare(iterate, lastIterate));
186             }
187 
188             // Check that each iterate is ordered.
189             for (int i = 1; i < iterate.length; i++) {
190                 assertTrue(iterate[i] > iterate[i - 1]);
191             }
192 
193             lastIterate = iterate;
194             ++numIterates;
195         }
196 
197         // Check the number of iterates.
198         assertEquals(CombinatoricsUtils.binomialCoefficient(n, k),
199                             numIterates);
200     }
201 
202     @Test
203     void testCombinationsIteratorFail() {
204         try {
205             new Combinations(4, 5).iterator();
206             fail("expecting MathIllegalArgumentException");
207         } catch (MathIllegalArgumentException ex) {
208             // ignored
209         }
210 
211         try {
212             new Combinations(-1, -2).iterator();
213             fail("expecting MathIllegalArgumentException");
214         } catch (MathIllegalArgumentException ex) {
215             // ignored
216         }
217     }
218 
219     @Test
220     void testLexicographicIteratorUnreachable() {
221         // this tests things that could really never happen,
222         // as the conditions are tested in the enclosing class before
223         // the lexicographic iterator is built
224         try {
225             Class<?> lexicographicIteratorClass = null;
226             for (Class<?> c : Combinations.class.getDeclaredClasses()) {
227                 if (c.getCanonicalName().endsWith(".LexicographicIterator")) {
228                     lexicographicIteratorClass = c;
229                 }
230             }
231             Constructor<?> ctr = lexicographicIteratorClass.getDeclaredConstructor(Integer.TYPE, Integer.TYPE);
232             Method hasNext = lexicographicIteratorClass.getDeclaredMethod("hasNext");
233             Method next = lexicographicIteratorClass.getDeclaredMethod("next");
234             Method remove = lexicographicIteratorClass.getDeclaredMethod("remove");
235             assertFalse((Boolean) hasNext.invoke(ctr.newInstance(3, 0)));
236             assertFalse((Boolean) hasNext.invoke(ctr.newInstance(3, 3)));
237             assertTrue((Boolean) hasNext.invoke(ctr.newInstance(3, 2)));
238             try {
239                 next.invoke(ctr.newInstance(3, 0));
240                 fail("an exception should have been thrown");
241             } catch (InvocationTargetException ite) {
242                 assertTrue(ite.getCause() instanceof NoSuchElementException);
243             }
244             try {
245                 remove.invoke(ctr.newInstance(3, 2));
246                 fail("an exception should have been thrown");
247             } catch (InvocationTargetException ite) {
248                 assertTrue(ite.getCause() instanceof UnsupportedOperationException);
249             }
250         } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException |
251                  InvocationTargetException | InstantiationException e) {
252             fail(e.getLocalizedMessage());
253         }
254     }
255 
256     @Test
257     void testSingletonIteratorUnreachable() {
258         // this tests things that could really never happen,
259         try {
260             Class<?> singletonIteratorClass = null;
261             for (Class<?> c : Combinations.class.getDeclaredClasses()) {
262                 if (c.getCanonicalName().endsWith(".SingletonIterator")) {
263                     singletonIteratorClass = c;
264                 }
265             }
266             Constructor<?> ctr = singletonIteratorClass.getDeclaredConstructor(Integer.TYPE);
267             Method hasNext = singletonIteratorClass.getDeclaredMethod("hasNext");
268             Method next = singletonIteratorClass.getDeclaredMethod("next");
269             Method remove = singletonIteratorClass.getDeclaredMethod("remove");
270             Object iterator = ctr.newInstance(3);
271             assertTrue((Boolean) hasNext.invoke(iterator));
272             int[] ret = (int[]) next.invoke(iterator);
273             assertEquals(3, ret.length);
274             assertEquals(0, ret[0]);
275             assertEquals(1, ret[1]);
276             assertEquals(2, ret[2]);
277             assertFalse((Boolean) hasNext.invoke(iterator));
278             try {
279                 next.invoke(iterator);
280                 fail("an exception should have been thrown");
281             } catch (InvocationTargetException ite) {
282                 assertTrue(ite.getCause() instanceof NoSuchElementException);
283             }
284             try {
285                 remove.invoke(iterator);
286                 fail("an exception should have been thrown");
287             } catch (InvocationTargetException ite) {
288                 assertTrue(ite.getCause() instanceof UnsupportedOperationException);
289             }
290         } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException |
291                  InvocationTargetException | InstantiationException e) {
292             fail(e.getLocalizedMessage());
293         }
294     }
295 
296 }