[COLLECTIONS-422] Added CollectionUtils.permutations(Collection) and PermutationIterator. Thanks for Benoit Corne for the patch.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1470310 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
df593b1fa5
commit
0574dfb035
3
pom.xml
3
pom.xml
|
@ -177,6 +177,9 @@
|
|||
<contributor>
|
||||
<name>Steve Clark</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Benoit Corne</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Eric Crampton</name>
|
||||
</contributor>
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
<body>
|
||||
|
||||
<release version="4.0" date="TBA" description="Next release">
|
||||
<action issue="COLLECTIONS-422" dev="tn" type="add" due-to="Benoit Corne">
|
||||
Added method "CollectionUtils#permutations(Collection)" and class "PermutationIterator"
|
||||
to generate unordered permutations of a collection.
|
||||
</action>
|
||||
<action issue="COLLECTIONS-419" dev="tn" type="fix" due-to="Adrian Nistor">
|
||||
Added clarifying javadoc wrt runtime complexity of "AbstractDualBidiMap#retainAll".
|
||||
</action>
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Enumeration;
|
|||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
@ -37,6 +38,7 @@ import org.apache.commons.collections4.collection.UnmodifiableBoundedCollection;
|
|||
import org.apache.commons.collections4.collection.UnmodifiableCollection;
|
||||
import org.apache.commons.collections4.functors.Equator;
|
||||
import org.apache.commons.collections4.functors.TruePredicate;
|
||||
import org.apache.commons.collections4.iterators.PermutationIterator;
|
||||
|
||||
/**
|
||||
* Provides utility methods and decorators for {@link Collection} instances.
|
||||
|
@ -1585,6 +1587,34 @@ public class CollectionUtils {
|
|||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a {@link Collection} of all the permutations of the input collection.
|
||||
* <p>
|
||||
* NOTE: the number of permutations of a given collection is equal to n!, where
|
||||
* n is the size of the collection. Thus, the resulting collection will become
|
||||
* <b>very</b> large for collections > 10 (e.g. 10! = 3628800, 15! = 1307674368000).
|
||||
* <p>
|
||||
* For larger collections it is advised to use a {@link PermutationIterator} to
|
||||
* iterate over all permutations.
|
||||
*
|
||||
* @see PermutationIterator
|
||||
*
|
||||
* @param <E> the element type
|
||||
* @param collection the collection to create permutations for, may not be null
|
||||
* @return an unordered collection of all permutations of the input collection
|
||||
* @throws NullPointerException if collection is null
|
||||
*/
|
||||
public static <E> Collection<List<E>> permutations(final Collection<E> collection) {
|
||||
final PermutationIterator<E> it = new PermutationIterator<E>(collection);
|
||||
final Collection<List<E>> result = new LinkedList<List<E>>();
|
||||
while (it.hasNext()) {
|
||||
result.add(it.next());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
/**
|
||||
* Returns a collection containing all the elements in <code>collection</code>
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* http://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.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* This iterator creates permutations of an input collection, using the
|
||||
* Steinhaus-Johnson-Trotter algorithm (also called plain changes).
|
||||
* <p>
|
||||
* The iterator will return exactly n! permutations of the input collection.
|
||||
* The {@code remove()} operation is not supported, and will throw an
|
||||
* {@code UnsupportedOperationException}.
|
||||
* <p>
|
||||
* NOTE: in case an empty collection is provided, the iterator will
|
||||
* return exactly one empty list as result, as 0! = 1.
|
||||
*
|
||||
* @param <E> the type of the objects being permuted
|
||||
*
|
||||
* @version $Id$
|
||||
* @since 4.0
|
||||
*/
|
||||
public class PermutationIterator<E> implements Iterator<List<E>> {
|
||||
|
||||
/**
|
||||
* Permutation is done on theses keys to handle equal objects.
|
||||
*/
|
||||
private int[] keys;
|
||||
|
||||
/**
|
||||
* Mapping between keys and objects.
|
||||
*/
|
||||
private Map<Integer, E> objectMap;
|
||||
|
||||
/**
|
||||
* Direction table used in the algorithm:
|
||||
* <ul>
|
||||
* <li>false is left</li>
|
||||
* <li>true is right</li>
|
||||
* </ul>
|
||||
*/
|
||||
private boolean[] direction;
|
||||
|
||||
/**
|
||||
* Next permutation to return. When a permutation is requested
|
||||
* this instance is provided and the next one is computed.
|
||||
*/
|
||||
private List<E> nextPermutation;
|
||||
|
||||
/**
|
||||
* Standard constructor for this class.
|
||||
* @param coll the collection to generate permutations for
|
||||
* @throws NullPointerException if coll is null
|
||||
*/
|
||||
public PermutationIterator(final Collection<E> coll) {
|
||||
if (coll == null) {
|
||||
throw new NullPointerException("The collection must not be null");
|
||||
}
|
||||
|
||||
keys = new int[coll.size()];
|
||||
direction = new boolean[coll.size()];
|
||||
Arrays.fill(direction, false);
|
||||
int value = 1;
|
||||
objectMap = new HashMap<Integer, E>();
|
||||
for (E e : coll) {
|
||||
objectMap.put(value, e);
|
||||
keys[value - 1] = value;
|
||||
value++;
|
||||
}
|
||||
nextPermutation = new ArrayList<E>(coll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there are more permutation available.
|
||||
* @return true if there are more permutations, otherwise false
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return nextPermutation != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next permutation of the input collection.
|
||||
* @return a list of the permutator's elements representing a permutation
|
||||
* @throws NoSuchElementException if there are no more permutations
|
||||
*/
|
||||
public List<E> next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
// find the largest mobile integer k
|
||||
int indexOfLargestMobileInteger = -1;
|
||||
int largestKey = -1;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if ((direction[i] && i < keys.length - 1 && keys[i] > keys[i + 1]) ||
|
||||
(!direction[i] && i > 0 && keys[i] > keys[i - 1])) {
|
||||
if (keys[i] > largestKey) {
|
||||
largestKey = keys[i];
|
||||
indexOfLargestMobileInteger = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (largestKey == -1) {
|
||||
List<E> toReturn = nextPermutation;
|
||||
nextPermutation = null;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// swap k and the adjacent integer it is looking at
|
||||
final int offset = direction[indexOfLargestMobileInteger] ? 1 : -1;
|
||||
final int tmpKey = keys[indexOfLargestMobileInteger];
|
||||
keys[indexOfLargestMobileInteger] = keys[indexOfLargestMobileInteger + offset];
|
||||
keys[indexOfLargestMobileInteger + offset] = tmpKey;
|
||||
boolean tmpDirection = direction[indexOfLargestMobileInteger];
|
||||
direction[indexOfLargestMobileInteger] = direction[indexOfLargestMobileInteger + offset];
|
||||
direction[indexOfLargestMobileInteger + offset] = tmpDirection;
|
||||
|
||||
// reverse the direction of all integers larger than k and build the result
|
||||
final List<E> nextP = new ArrayList<E>();
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i] > largestKey) {
|
||||
direction[i] = !direction[i];
|
||||
}
|
||||
nextP.add(objectMap.get(keys[i]));
|
||||
}
|
||||
final List<E> result = nextPermutation;
|
||||
nextPermutation = nextP;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("remove() is not supported");
|
||||
}
|
||||
|
||||
}
|
|
@ -1645,4 +1645,23 @@ public class CollectionUtilsTest extends MockTestCase {
|
|||
assertEquals("Merge two lists 2 - ignore duplicates", combinedList, result2);
|
||||
}
|
||||
|
||||
@Test(expected=NullPointerException.class)
|
||||
public void testPermutationsWithNullCollection() {
|
||||
CollectionUtils.permutations(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPermutations() {
|
||||
List<Integer> sample = collectionA.subList(0, 5);
|
||||
Collection<List<Integer>> permutations = CollectionUtils.permutations(sample);
|
||||
|
||||
// result size = n!
|
||||
int collSize = sample.size();
|
||||
int factorial = 1;
|
||||
for (int i = 1; i <= collSize; i++) {
|
||||
factorial *= i;
|
||||
}
|
||||
assertEquals(factorial, permutations.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* 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
|
||||
*
|
||||
* http://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.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Test class for PermutationIterator.
|
||||
*
|
||||
* @version $Id$
|
||||
* @since 4.0
|
||||
*/
|
||||
public class PermutationIteratorTest extends AbstractIteratorTest<List<Character>> {
|
||||
|
||||
protected Character[] testArray = { 'A', 'B', 'C' };
|
||||
protected List<Character> testList;
|
||||
|
||||
public PermutationIteratorTest(final String testName) {
|
||||
super(testName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
testList = new ArrayList<Character>();
|
||||
testList.addAll(Arrays.asList(testArray));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
public boolean supportsRemove() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsEmptyIterator() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermutationIterator<Character> makeEmptyIterator() {
|
||||
return new PermutationIterator<Character>(new ArrayList<Character>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermutationIterator<Character> makeObject() {
|
||||
return new PermutationIterator<Character>(testList);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
public void testPermutationResultSize() {
|
||||
int factorial = 1;
|
||||
for (int i = 0; i < 8; i++, factorial*=i) {
|
||||
List<Integer> list = new ArrayList<Integer>();
|
||||
for (int j = 0; j < i; j++) {
|
||||
list.add(j);
|
||||
}
|
||||
Iterator<List<Integer>> it = new PermutationIterator<Integer>(list);
|
||||
int count = 0;
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
count++;
|
||||
}
|
||||
assertEquals(factorial, count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* test checking that all the permutations are returned
|
||||
*/
|
||||
public void testPermutationExhaustivity() {
|
||||
List<Character> perm1 = new ArrayList<Character>();
|
||||
List<Character> perm2 = new ArrayList<Character>();
|
||||
List<Character> perm3 = new ArrayList<Character>();
|
||||
List<Character> perm4 = new ArrayList<Character>();
|
||||
List<Character> perm5 = new ArrayList<Character>();
|
||||
List<Character> perm6 = new ArrayList<Character>();
|
||||
|
||||
perm1.add('A');
|
||||
perm2.add('A');
|
||||
perm3.add('B');
|
||||
perm4.add('B');
|
||||
perm5.add('C');
|
||||
perm6.add('C');
|
||||
|
||||
perm1.add('B');
|
||||
perm2.add('C');
|
||||
perm3.add('A');
|
||||
perm4.add('C');
|
||||
perm5.add('A');
|
||||
perm6.add('B');
|
||||
|
||||
perm1.add('C');
|
||||
perm2.add('B');
|
||||
perm3.add('C');
|
||||
perm4.add('A');
|
||||
perm5.add('B');
|
||||
perm6.add('A');
|
||||
|
||||
List<List<Character>> results = new ArrayList<List<Character>>();
|
||||
|
||||
PermutationIterator<Character> it = makeObject();
|
||||
while (it.hasNext()) {
|
||||
List<Character> next = it.next();
|
||||
results.add(next);
|
||||
}
|
||||
//3! permutation for 3 elements
|
||||
assertEquals(6, results.size());
|
||||
assertTrue(results.contains(perm1));
|
||||
assertTrue(results.contains(perm2));
|
||||
assertTrue(results.contains(perm3));
|
||||
assertTrue(results.contains(perm4));
|
||||
assertTrue(results.contains(perm5));
|
||||
assertTrue(results.contains(perm6));
|
||||
}
|
||||
|
||||
/**
|
||||
* test checking that all the permutations are returned only once.
|
||||
*/
|
||||
public void testPermutationUnicity() {
|
||||
List<List<Character>> resultsList = new ArrayList<List<Character>>();
|
||||
Set<List<Character>> resultsSet = new HashSet<List<Character>>();
|
||||
|
||||
PermutationIterator<Character> it = makeObject();
|
||||
while (it.hasNext()) {
|
||||
List<Character> permutation = it.next();
|
||||
resultsList.add(permutation);
|
||||
resultsSet.add(permutation);
|
||||
}
|
||||
//3! permutation for 3 elements
|
||||
assertEquals(6, resultsList.size());
|
||||
assertEquals(6, resultsSet.size());
|
||||
}
|
||||
|
||||
public void testPermutationException() {
|
||||
List<List<Character>> resultsList = new ArrayList<List<Character>>();
|
||||
|
||||
PermutationIterator<Character> it = makeObject();
|
||||
while (it.hasNext()) {
|
||||
List<Character> permutation = it.next();
|
||||
resultsList.add(permutation);
|
||||
}
|
||||
//asking for another permutation should throw an exception
|
||||
try {
|
||||
it.next();
|
||||
fail();
|
||||
} catch (NoSuchElementException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testPermutatorHasMore() {
|
||||
PermutationIterator<Character> it = makeObject();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
assertTrue(it.hasNext());
|
||||
it.next();
|
||||
}
|
||||
assertFalse(it.hasNext());
|
||||
}
|
||||
|
||||
public void testEmptyCollection() {
|
||||
PermutationIterator<Character> it = makeEmptyIterator();
|
||||
// there is one permutation for an empty set: 0! = 1
|
||||
assertTrue(it.hasNext());
|
||||
|
||||
List<Character> nextPermutation = it.next();
|
||||
assertEquals(0, nextPermutation.size());
|
||||
|
||||
assertFalse(it.hasNext());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue