Author: tn Date: Thu Jan 1 13:51:44 2015 New Revision: 1648844 URL: http://svn.apache.org/r1648844 Log: [COLLECTIONS-511] Added CollectionUtils.partition(...) methods. Thanks to Brent Worden, Nathan Blomquist.
Modified: commons/proper/collections/trunk/src/changes/changes.xml commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/CollectionUtils.java commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java Modified: commons/proper/collections/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/changes/changes.xml?rev=1648844&r1=1648843&r2=1648844&view=diff ============================================================================== --- commons/proper/collections/trunk/src/changes/changes.xml (original) +++ commons/proper/collections/trunk/src/changes/changes.xml Thu Jan 1 13:51:44 2015 @@ -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. Modified: commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/CollectionUtils.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/CollectionUtils.java?rev=1648844&r1=1648843&r2=1648844&view=diff ============================================================================== --- commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/CollectionUtils.java (original) +++ commons/proper/collections/trunk/src/main/java/org/apache/commons/collections4/CollectionUtils.java Thu Jan 1 13:51:44 2015 @@ -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; @@ -991,6 +992,208 @@ public class CollectionUtils { } /** + * 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 < 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 < 3] + * and [x < 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 < 3] + * and [x < 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. * <p> Modified: commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java URL: http://svn.apache.org/viewvc/commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java?rev=1648844&r1=1648843&r2=1648844&view=diff ============================================================================== --- commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java (original) +++ commons/proper/collections/trunk/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java Thu Jan 1 13:51:44 2015 @@ -50,6 +50,7 @@ import org.apache.commons.collections4.c 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 } }; + 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() { @@ -1179,6 +1186,87 @@ public class CollectionUtilsTest extends } @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); Collection<Number> collection = CollectionUtils.<Integer, Number>collect(iterableA, transformer);