From 04b05e725d09fe077c12a67be5639f57b5612e9f Mon Sep 17 00:00:00 2001
From: Igor Rudenko
+ * The iterator might return fewer than n! permutations of the input collection,
+ * because duplicated permutations are skipped: equal elements are not
+ * distinguished from one another.
+ * The {@code remove()} operation is not supported, and will throw an
+ * {@code UnsupportedOperationException}.
+ *
+ * NOTE: in case an empty collection is provided, the iterator will
+ * return exactly one empty list as result, as 0! = 1.
+ *
+ * Iteration starts at the arrangement in which the elements are given, and each
+ * call to {@code next()} advances to the smallest arrangement greater than the
+ * current one. Iteration therefore ends at the largest arrangement, and the ones
+ * preceding the given arrangement are never returned: only a collection already
+ * sorted according to the ordering in use yields the complete set of
+ * permutations. Callers wanting the complete set must sort the collection
+ * beforehand, just as callers of
+ * {@link java.util.Collections#binarySearch(java.util.List, Object) binarySearch}
+ * must. Callers wanting to enumerate one part of the set, to resume from a
+ * previously reached arrangement or to split the work, may start anywhere.
+ *
+ * The starting arrangement is the iteration order of the given collection, so
+ * collections whose iteration order is unspecified, such as {@link java.util.HashSet},
+ * make poor input: which permutations are returned is then unspecified too.
+ *
* The iterator might return fewer than n! permutations of the input collection,
- * because duplicated permutations are skipped: equal elements are not
- * distinguished from one another.
+ * either because the collection was not sorted according to the ordering in use,
+ * as described above, or because duplicated permutations are skipped: equal
+ * elements are not distinguished from one another.
* The {@code remove()} operation is not supported, and will throw an
* {@code UnsupportedOperationException}.
* > that generates the permutations of a collection in
lexicographical order, complementing PermutationIterator, which uses the
Steinhaus-Johnson-Trotter ordering.
Elements are ordered by their natural ordering, or by a Comparator supplied
to the two-argument constructor, which also allows permuting elements that
do not implement Comparable. Each call to next() advances by the standard
next-permutation step: locate the pivot, swap it with its successor, then
reverse the descending tail. Equal elements are not distinguished, so an
input with duplicates yields fewer than n! permutations. An empty collection
yields exactly one empty list, as 0! = 1. remove() is unsupported.
Comparator dispatch follows the java.util.TreeMap pattern of testing the
comparator field for null on each comparison; benchmarking showed no
measurable difference against normalizing null to Comparator.naturalOrder()
in the constructor.
Tests extend AbstractIteratorTest to cover the Iterator contract, and add
cases for lexicographical exhaustivity, duplicate handling, custom and
reverse comparators, non-Comparable elements, stream traversal, exhaustion,
and equals/hashCode.
---
.../LexicographicPermutationIterator.java | 198 ++++++++++
.../LexicographicPermutationIteratorTest.java | 343 ++++++++++++++++++
2 files changed, 541 insertions(+)
create mode 100644 src/main/java/org/apache/commons/collections4/iterators/LexicographicPermutationIterator.java
create mode 100644 src/test/java/org/apache/commons/collections4/iterators/LexicographicPermutationIteratorTest.java
diff --git a/src/main/java/org/apache/commons/collections4/iterators/LexicographicPermutationIterator.java b/src/main/java/org/apache/commons/collections4/iterators/LexicographicPermutationIterator.java
new file mode 100644
index 0000000000..3389db68b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/collections4/iterators/LexicographicPermutationIterator.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * https://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.apache.commons.collections4.iterators;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * This iterator creates permutations of an input collection, using the
+ * lexicographical order.
+ *
> {
+
+ /**
+ * The comparator used to define order of generation,
+ * or null if it uses the natural ordering.
+ */
+ private final Comparator super E> comparator;
+
+ /**
+ * Next permutation to return. When a permutation is requested
+ * this instance is provided and the next one is computed.
+ */
+ private List
> {
+
+ /**
+ * A comparator that orders nothing, identified only by an id, used to check that
+ * equal comparators make equal iterators.
+ *
+ * @param
> permutationIterator = new LexicographicPermutationIterator<>(Arrays.asList('C', 'B', 'A'),
+ Comparator.reverseOrder());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('C', 'B', 'A'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('C', 'A', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'C', 'A'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'A', 'C'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'C', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'B', 'C'), permutationIterator.next());
+
+ assertFalse(permutationIterator.hasNext());
+ }
+
+ @Test
+ void testCustomComparatorWithNonComparableObjects() {
+ final Iterator
>> permutationIterator =
+ new LexicographicPermutationIterator<>(Arrays.asList(
+ new NonComparableObject<>('A'),
+ new NonComparableObject<>('B')), Comparator.comparing(NonComparableObject::getValue));
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList(
+ new NonComparableObject<>('A'),
+ new NonComparableObject<>('B')), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList(
+ new NonComparableObject<>('B'),
+ new NonComparableObject<>('A')), permutationIterator.next());
+
+ assertFalse(permutationIterator.hasNext());
+ }
+
+ @Test
+ void testDuplicatedPermutationsAreSkipped() {
+ final Iterator
> permutationIterator = new LexicographicPermutationIterator<>(Arrays.asList('A', 'A', 'B', 'B'));
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'A', 'B', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'B', 'A', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'B', 'B', 'A'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'A', 'A', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'A', 'B', 'A'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'B', 'A', 'A'), permutationIterator.next());
+
+ assertFalse(permutationIterator.hasNext());
+ }
+
+ @Test
+ void testEmptyCollection() {
+ final Iterator
> permutationIterator = makeEmptyIterator();
+
+ // there is one permutation for an empty set: 0! = 1
+ assertTrue(permutationIterator.hasNext());
+ assertTrue(permutationIterator.next().isEmpty());
+
+ assertFalse(permutationIterator.hasNext());
+ }
+
+ @Test
+ void testEqualsForEqualCollections() {
+ final Iterator
> one = new LexicographicPermutationIterator<>(emptyList());
+ final Iterator
> another = new LexicographicPermutationIterator<>(emptyList());
+
+ assertEquals(one, another);
+ }
+
+ @Test
+ void testEqualsForEqualCollectionsAndComparators() {
+ final Iterator
> one = new LexicographicPermutationIterator<>(emptyList(), new CustomComparator<>(42));
+ final Iterator
> another = new LexicographicPermutationIterator<>(emptyList(), new CustomComparator<>(42));
+
+ assertEquals(one, another);
+ }
+
+ @Test
+ void testHashCodeForEqualCollections() {
+ final Iterator
> one = new LexicographicPermutationIterator<>(emptyList());
+ final Iterator
> another = new LexicographicPermutationIterator<>(emptyList());
+
+ assertEquals(one.hashCode(), another.hashCode());
+ }
+
+ @Test
+ void testHashCodeForEqualCollectionsAndComparators() {
+ final Iterator
> one = new LexicographicPermutationIterator<>(emptyList(), new CustomComparator<>(42));
+ final Iterator
> another = new LexicographicPermutationIterator<>(emptyList(), new CustomComparator<>(42));
+
+ assertEquals(one.hashCode(), another.hashCode());
+ }
+
+ @Test
+ void testNonComparableElementsThrow() {
+ final Iterator
>> permutationIterator = new LexicographicPermutationIterator<>(
+ Arrays.asList(
+ new NonComparableObject<>('A'),
+ new NonComparableObject<>('B')));
+
+ assertTrue(permutationIterator.hasNext());
+ assertThrows(ClassCastException.class, permutationIterator::next);
+ }
+
+ @Test
+ void testPermutationException() {
+ final Iterator
> permutationIterator = new LexicographicPermutationIterator<>(Arrays.asList('A', 'B'));
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'A'), permutationIterator.next());
+
+ // asking for another permutation should throw an exception
+ assertFalse(permutationIterator.hasNext());
+ assertThrows(NoSuchElementException.class, permutationIterator::next);
+ }
+
+ /**
+ * test checking that all the permutations are returned in lexicographical order
+ */
+ @Test
+ void testPermutationExhaustivity() {
+ final Iterator
> permutationIterator = makeObject();
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'B', 'C'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('A', 'C', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'A', 'C'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('B', 'C', 'A'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('C', 'A', 'B'), permutationIterator.next());
+
+ assertTrue(permutationIterator.hasNext());
+ assertEquals(Arrays.asList('C', 'B', 'A'), permutationIterator.next());
+
+ assertFalse(permutationIterator.hasNext());
+ }
+
+ @Test
+ void testRemoveThrows() {
+ final Iterator
> permutationIterator = makeObject();
+
+ assertTrue(permutationIterator.hasNext());
+ assertThrows(UnsupportedOperationException.class, permutationIterator::remove);
+ }
+
+ @Test
+ void testStreamOfPermutations() {
+ final Iterable
> iterable = this::makeObject;
+
+ final List
> allPermutations = StreamSupport.stream(iterable.spliterator(), false)
+ .collect(Collectors.toList());
+
+ assertEquals(Arrays.asList(
+ Arrays.asList('A', 'B', 'C'),
+ Arrays.asList('A', 'C', 'B'),
+ Arrays.asList('B', 'A', 'C'),
+ Arrays.asList('B', 'C', 'A'),
+ Arrays.asList('C', 'A', 'B'),
+ Arrays.asList('C', 'B', 'A')), allPermutations);
+ }
+
+}
From fb5995493234fd1e218a344e2032684e6ba2536c Mon Sep 17 00:00:00 2001
From: Igor Rudenko
+ * NOTE: {@link PermutationIterator} differs on both counts. It returns exactly n! + * permutations whatever the order of the input collection. The two iterators are + * therefore not interchangeable. + *
* * @param+ * Iteration starts at the arrangement in which the collection iterates its + * elements; sort the collection first to obtain the complete set of permutations. + *
* * @param collection The collection to generate permutations for * @throws NullPointerException if collection is null @@ -70,6 +97,11 @@ public LexicographicPermutationIterator(final Collection extends E> collection /** * Constructs an instance using the given comparator to order the elements. + *+ * Iteration starts at the arrangement in which the collection iterates its + * elements; sort the collection with the same comparator first to obtain the + * complete set of permutations. + *
* * @param collection The collection to generate permutations for * @param comparator The comparator used to define the order of generation, @@ -97,6 +129,8 @@ public boolean hasNext() { * * @return A list of the permutator's elements representing a permutation * @throws NoSuchElementException if there are no more permutations + * @throws ClassCastException if no comparator was supplied and the elements are + * not mutually {@link Comparable} */ @Override public List