[COLLECTIONS-511] Added CollectionUtils.partition(...) methods. Thanks to Brent Worden, Nathan Blomquist.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1648844 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2015-01-01 13:51:44 +00:00
parent 8cb5cbc601
commit 90f4139bf7
3 changed files with 295 additions and 0 deletions

View File

@ -22,6 +22,10 @@
<body>
<release version="4.1" date="TBD" description="">
<action issue="COLLECTIONS-511" dev="tn" type="add" due-to="Nathan Blomquist, Brent Worden">
Added new methods "CollectionUtils#partition(...)" to partition an input collection
into separate output collections based on evaluation of one or more predicates.
</action>
<action issue="COLLECTIONS-537" dev="tn" type="fix" due-to="Frank Jakop">
Harmonized signature of factory methods for functor-related classes which take
a collection as input with their array counterparts.

View File

@ -19,6 +19,7 @@ package org.apache.commons.collections4;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
@ -990,6 +991,208 @@ public class CollectionUtils {
return outputCollection;
}
/**
* Partitions all elements from inputCollection into separate output collections,
* based on the evaluation of the given predicate.
* <p>
* For each predicate, the returned list will contain a collection holding
* all elements of the input collection matching the predicate. The last collection
* contained in the list will hold all elements which didn't match any predicate:
* <pre>
* [C1, R] = partition(I, P1) with
* I = input collection
* P1 = first predicate
* C1 = collection of elements matching P1
* R = collection of elements rejected by all predicates
* </pre>
* <p>
* If the input collection is <code>null</code>, an empty list will be returned.
* If the input predicate is <code>null</code>, all elements of the input collection
* will be added to the rejected collection.
* <p>
* Example: for an input list [1, 2, 3, 4, 5] calling partition with a predicate [x &lt; 3]
* will result in the following output: [[1, 2], [3, 4, 5]].
*
* @param <O> the type of object the {@link Iterable} contains
* @param <R> the type of the output {@link Collection}
* @param inputCollection the collection to get the input from, may be null
* @param predicate the predicate to use, may be null
* @return a list containing the output collections
* @since 4.1
*/
public static <O, R extends Collection<O>> List<R> partition(final Iterable<? extends O> inputCollection,
final Predicate<? super O> predicate) {
@SuppressWarnings("unchecked") // safe
final Class<R> outputClass = (Class<R>) ArrayList.class;
@SuppressWarnings("unchecked") // safe
final Predicate<? super O>[] predicates = new Predicate[] { predicate };
return partition(inputCollection, FactoryUtils.instantiateFactory(outputClass), predicates);
}
/**
* Partitions all elements from inputCollection into an output and rejected collection,
* based on the evaluation of the given predicate.
* <p>
* Elements matching the predicate are added to the <code>outputCollection</code>,
* all other elements are added to the <code>rejectedCollection</code>.
* <p>
* If the input predicate is <code>null</code>, no elements are added to
* <code>outputCollection</code> or <code>rejectedCollection</code>.
* <p>
* Note: calling the method is equivalent to the following code snippet:
* <pre>
* select(inputCollection, predicate, outputCollection);
* selectRejected(inputCollection, predicate, rejectedCollection);
* </pre>
*
* @param <O> the type of object the {@link Iterable} contains
* @param <R> the type of the output {@link Collection}
* @param inputCollection the collection to get the input from, may be null
* @param predicate the predicate to use, may be null
* @param outputCollection the collection to output selected elements into, may not be null if the
* inputCollection and predicate are not null
* @param rejectedCollection the collection to output rejected elements into, may not be null if the
* inputCollection or predicate are not null
* @since 4.1
*/
public static <O, R extends Collection<? super O>> void partition(final Iterable<? extends O> inputCollection,
final Predicate<? super O> predicate, R outputCollection, R rejectedCollection) {
if (inputCollection != null && predicate != null) {
for (final O element : inputCollection) {
if (predicate.evaluate(element)) {
outputCollection.add(element);
} else {
rejectedCollection.add(element);
}
}
}
}
/**
* Partitions all elements from inputCollection into separate output collections,
* based on the evaluation of the given predicates.
* <p>
* For each predicate, the returned list will contain a collection holding
* all elements of the input collection matching the predicate. The last collection
* contained in the list will hold all elements which didn't match any predicate:
* <pre>
* [C1, C2, R] = partition(I, P1, P2) with
* I = input collection
* P1 = first predicate
* P2 = second predicate
* C1 = collection of elements matching P1
* C2 = collection of elements matching P2
* R = collection of elements rejected by all predicates
* </pre>
* <p>
* <b>Note</b>: elements are only added to the output collection of the first matching
* predicate, determined by the order of arguments.
* <p>
* If the input collection is <code>null</code>, an empty list will be returned.
* If the input predicate is <code>null</code>, all elements of the input collection
* will be added to the rejected collection.
* <p>
* Example: for an input list [1, 2, 3, 4, 5] calling partition with predicates [x &lt; 3]
* and [x &lt; 5] will result in the following output: [[1, 2], [3, 4], [5]].
*
* @param <O> the type of object the {@link Iterable} contains
* @param <R> the type of the output {@link Collection}
* @param inputCollection the collection to get the input from, may be null
* @param predicates the predicates to use, may be null
* @return a list containing the output collections
* @since 4.1
*/
public static <O, R extends Collection<O>> List<R> partition(final Iterable<? extends O> inputCollection,
final Predicate<? super O>... predicates) {
@SuppressWarnings("unchecked") // safe
final Class<R> outputClass = (Class<R>) ArrayList.class;
return partition(inputCollection, FactoryUtils.instantiateFactory(outputClass), predicates);
}
/**
* Partitions all elements from inputCollection into separate output collections,
* based on the evaluation of the given predicates.
* <p>
* For each predicate, the returned list will contain a collection holding
* all elements of the input collection matching the predicate. The last collection
* contained in the list will hold all elements which didn't match any predicate:
* <pre>
* [C1, C2, R] = partition(I, P1, P2) with
* I = input collection
* P1 = first predicate
* P2 = second predicate
* C1 = collection of elements matching P1
* C2 = collection of elements matching P2
* R = collection of elements rejected by all predicates
* </pre>
* <p>
* <b>Note</b>: elements are only added to the output collection of the first matching
* predicate, determined by the order of arguments.
* <p>
* If the input collection is <code>null</code>, an empty list will be returned.
* If no predicates have been provided, all elements of the input collection
* will be added to the rejected collection.
* <p>
* Example: for an input list [1, 2, 3, 4, 5] calling partition with predicates [x &lt; 3]
* and [x &lt; 5] will result in the following output: [[1, 2], [3, 4], [5]].
*
* @param <O> the type of object the {@link Iterable} contains
* @param <R> the type of the output {@link Collection}
* @param inputCollection the collection to get the input from, may be null
* @param partitionFactory the factory used to create the output collections
* @param predicates the predicates to use, may be empty
* @return a list containing the output collections
* @since 4.1
*/
public static <O, R extends Collection<O>> List<R> partition(final Iterable<? extends O> inputCollection,
final Factory<R> partitionFactory, final Predicate<? super O>... predicates) {
if (inputCollection == null) {
return Collections.emptyList();
}
if (predicates == null || predicates.length < 1) {
// return the entire input collection as a single partition
final R singlePartition = partitionFactory.create();
select(inputCollection, PredicateUtils.truePredicate(), singlePartition);
return Collections.singletonList(singlePartition);
}
// create the empty partitions
final int numberOfPredicates = predicates.length;
final int numberOfPartitions = numberOfPredicates + 1;
final List<R> partitions = new ArrayList<R>(numberOfPartitions);
for (int i = 0; i < numberOfPartitions; ++i) {
partitions.add(partitionFactory.create());
}
// for each element in inputCollection:
// find the first predicate that evaluates to true.
// if there is a predicate, add the element to the corresponding partition.
// if there is no predicate, add it to the last, catch-all partition.
for (final O element : inputCollection) {
boolean elementAssigned = false;
for (int i = 0; i < numberOfPredicates; ++i) {
if (predicates[i].evaluate(element)) {
partitions.get(i).add(element);
elementAssigned = true;
break;
}
}
if (!elementAssigned) {
// no predicates evaluated to true
// add element to last partition
partitions.get(numberOfPredicates).add(element);
}
}
return partitions;
}
/**
* Returns a new Collection consisting of the elements of inputCollection
* transformed by the given transformer.

View File

@ -50,6 +50,7 @@ import org.apache.commons.collections4.collection.TransformedCollection;
import org.apache.commons.collections4.collection.UnmodifiableCollection;
import org.apache.commons.collections4.functors.DefaultEquator;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -1067,6 +1068,12 @@ public class CollectionUtilsTest extends MockTestCase {
}
};
private static Predicate<Number> EVEN = new Predicate<Number>() {
public boolean evaluate(final Number input) {
return input.intValue() % 2 == 0;
}
};
//Up to here
@Test
public void filter() {
@ -1178,6 +1185,87 @@ public class CollectionUtilsTest extends MockTestCase {
assertTrue(output1.contains(4L));
}
@Test
public void partition() {
List<Integer> input = new ArrayList<Integer>();
input.add(1);
input.add(2);
input.add(3);
input.add(4);
List<Collection<Integer>> partitions = CollectionUtils.partition(input, EQUALS_TWO);
assertEquals(2, partitions.size());
// first partition contains 2
Collection<Integer> partition = partitions.get(0);
assertEquals(1, partition.size());
assertEquals(2, CollectionUtils.extractSingleton(partition).intValue());
// second partition contains 1, 3, and 4
Integer[] expected = {1, 3, 4};
partition = partitions.get(1);
Assert.assertArrayEquals(expected, partition.toArray());
partitions = CollectionUtils.partition((List<Integer>) null, EQUALS_TWO);
assertTrue(partitions.isEmpty());
partitions = CollectionUtils.partition(input);
assertEquals(1, partitions.size());
assertEquals(input, partitions.get(0));
}
@Test
public void partitionWithOutputCollections() {
List<Integer> input = new ArrayList<Integer>();
input.add(1);
input.add(2);
input.add(3);
input.add(4);
List<Integer> output = new ArrayList<Integer>();
List<Integer> rejected = new ArrayList<Integer>();
CollectionUtils.partition(input, EQUALS_TWO, output, rejected);
// output contains 2
assertEquals(1, output.size());
assertEquals(2, CollectionUtils.extractSingleton(output).intValue());
// rejected contains 1, 3, and 4
Integer[] expected = {1, 3, 4};
Assert.assertArrayEquals(expected, rejected.toArray());
output.clear();
rejected.clear();
CollectionUtils.partition((List<Integer>) null, EQUALS_TWO, output, rejected);
assertTrue(output.isEmpty());
assertTrue(rejected.isEmpty());
}
@Test
public void partitionMultiplePredicates() {
List<Integer> input = new ArrayList<Integer>();
input.add(1);
input.add(2);
input.add(3);
input.add(4);
List<Collection<Integer>> partitions = CollectionUtils.partition(input, EQUALS_TWO, EVEN);
// first partition contains 2
Collection<Integer> partition = partitions.get(0);
assertEquals(1, partition.size());
assertEquals(2, partition.iterator().next().intValue());
// second partition contains 4
partition = partitions.get(1);
assertEquals(1, partition.size());
assertEquals(4, partition.iterator().next().intValue());
// third partition contains 1 and 3
Integer[] expected = {1, 3};
partition = partitions.get(2);
Assert.assertArrayEquals(expected, partition.toArray());
}
@Test
public void collect() {
final Transformer<Number, Long> transformer = TransformerUtils.constantTransformer(2L);