From 5214f666185e28be8bb6a19bee96f174d62025ff Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Sat, 6 Jun 2015 08:54:54 +0000 Subject: [PATCH] [COLLECTIONS-551] Move partition methods from CollectionUtils to IterableUtils. Make method consistent: do not allow null predicates, return the same for a null iterable as for an empty one. git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1683894 13f79535-47bb-0310-9956-ffa450edef68 --- src/changes/changes.xml | 2 +- .../commons/collections4/CollectionUtils.java | 243 +++--------------- .../commons/collections4/IterableUtils.java | 174 +++++++++++++ .../collections4/CollectionUtilsTest.java | 117 ++------- .../collections4/IterableUtilsTest.java | 77 ++++++ 5 files changed, 322 insertions(+), 291 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9541af494..5a646b75e 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -98,7 +98,7 @@ that are prefixed by the given search key in some rare cases. - Added new methods "CollectionUtils#partition(...)" to partition an input collection + Added new methods "IterableUtils#partition(...)" to partition an input collection into separate output collections based on evaluation of one or more predicates. diff --git a/src/main/java/org/apache/commons/collections4/CollectionUtils.java b/src/main/java/org/apache/commons/collections4/CollectionUtils.java index 0dfa43c76..171acc30b 100644 --- a/src/main/java/org/apache/commons/collections4/CollectionUtils.java +++ b/src/main/java/org/apache/commons/collections4/CollectionUtils.java @@ -19,7 +19,6 @@ 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; @@ -918,6 +917,48 @@ public class CollectionUtils { return outputCollection; } + /** + * Selects all elements from inputCollection into an output and rejected collection, + * based on the evaluation of the given predicate. + *

+ * Elements matching the predicate are added to the outputCollection, + * all other elements are added to the rejectedCollection. + *

+ * If the input predicate is null, no elements are added to + * outputCollection or rejectedCollection. + *

+ * Note: calling the method is equivalent to the following code snippet: + *

+     *   select(inputCollection, predicate, outputCollection);
+     *   selectRejected(inputCollection, predicate, rejectedCollection);
+     * 
+ * + * @param the type of object the {@link Iterable} contains + * @param 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 + * @return the outputCollection + * @since 4.1 + */ + public static > R select(final Iterable inputCollection, + final Predicate 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); + } + } + } + return outputCollection; + } + /** * Selects all elements from inputCollection which don't match the given * predicate into an output collection. @@ -966,206 +1007,6 @@ public class CollectionUtils { return outputCollection; } - /** - * Partitions all elements from inputCollection into separate output collections, - * based on the evaluation of the given predicate. - *

- * For each predicate, the result will contain a list holding all elements of the - * input collection matching the predicate. The last list will hold all elements - * which didn't match any predicate: - *

-     *  [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
-     * 
- *

- * If the input collection is null, an empty list will be returned. - * If the input predicate is null, all elements of the input collection - * will be added to the rejected collection. - *

- * 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 the type of object the {@link Iterable} contains - * @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 List> partition(final Iterable inputCollection, - final Predicate predicate) { - - @SuppressWarnings({ "unchecked", "rawtypes" }) // safe - final Factory> factory = FactoryUtils.instantiateFactory((Class) ArrayList.class); - @SuppressWarnings("unchecked") // safe - final Predicate[] predicates = new Predicate[] { predicate }; - return partition(inputCollection, factory, predicates); - } - - /** - * Partitions all elements from inputCollection into an output and rejected collection, - * based on the evaluation of the given predicate. - *

- * Elements matching the predicate are added to the outputCollection, - * all other elements are added to the rejectedCollection. - *

- * If the input predicate is null, no elements are added to - * outputCollection or rejectedCollection. - *

- * Note: calling the method is equivalent to the following code snippet: - *

-     *   select(inputCollection, predicate, outputCollection);
-     *   selectRejected(inputCollection, predicate, rejectedCollection);
-     * 
- * - * @param the type of object the {@link Iterable} contains - * @param 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 > void partition(final Iterable inputCollection, - final Predicate 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. - *

- * For each predicate, the result will contain a list holding all elements of the - * input collection matching the predicate. The last list will hold all elements - * which didn't match any predicate: - *

-     *  [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
-     * 
- *

- * Note: elements are only added to the output collection of the first matching - * predicate, determined by the order of arguments. - *

- * If the input collection is null, an empty list will be returned. - * If the input predicate is null, all elements of the input collection - * will be added to the rejected collection. - *

- * 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 the type of object the {@link Iterable} contains - * @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 List> partition(final Iterable inputCollection, - final Predicate... predicates) { - - @SuppressWarnings({ "unchecked", "rawtypes" }) // safe - final Factory> factory = FactoryUtils.instantiateFactory((Class) ArrayList.class); - return partition(inputCollection, factory, predicates); - } - - /** - * Partitions all elements from inputCollection into separate output collections, - * based on the evaluation of the given predicates. - *

- * 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: - *

-     *  [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
-     * 
- *

- * Note: elements are only added to the output collection of the first matching - * predicate, determined by the order of arguments. - *

- * If the input collection is null, 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. - *

- * 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 the type of object the {@link Iterable} contains - * @param 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 > List partition(final Iterable inputCollection, - final Factory partitionFactory, final Predicate... 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 partitions = new ArrayList(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 containing all elements of the input collection * transformed by the given transformer. diff --git a/src/main/java/org/apache/commons/collections4/IterableUtils.java b/src/main/java/org/apache/commons/collections4/IterableUtils.java index 015aad898..9a04619bf 100644 --- a/src/main/java/org/apache/commons/collections4/IterableUtils.java +++ b/src/main/java/org/apache/commons/collections4/IterableUtils.java @@ -16,7 +16,9 @@ */ package org.apache.commons.collections4; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -680,6 +682,178 @@ public class IterableUtils { } } + /** + * Partitions all elements from iterable into separate output collections, + * based on the evaluation of the given predicate. + *

+ * For each predicate, the result will contain a list holding all elements of the + * input iterable matching the predicate. The last list will hold all elements + * which didn't match any predicate: + *

+     *  [C1, R] = partition(I, P1) with
+     *  I = input
+     *  P1 = first predicate
+     *  C1 = collection of elements matching P1
+     *  R = collection of elements rejected by all predicates
+     * 
+ *

+ * If the input iterable is null, the same is returned as for an + * empty iterable. + *

+ * 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 the type of object the {@link Iterable} contains + * @param iterable the iterable to partition, may be null + * @param predicate the predicate to use, may not be null + * @return a list containing the output collections + * @throws NullPointerException if predicate is null + */ + public static List> partition(final Iterable iterable, + final Predicate predicate) { + if (predicate == null) { + throw new NullPointerException("Predicate must not be null."); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) // safe + final Factory> factory = FactoryUtils.instantiateFactory((Class) ArrayList.class); + @SuppressWarnings("unchecked") // safe + final Predicate[] predicates = new Predicate[] { predicate }; + return partition(iterable, factory, predicates); + } + + /** + * Partitions all elements from iterable into separate output collections, + * based on the evaluation of the given predicates. + *

+ * For each predicate, the result will contain a list holding all elements of the + * input iterable matching the predicate. The last list will hold all elements + * which didn't match any predicate: + *

+     *  [C1, C2, R] = partition(I, P1, P2) with
+     *  I = input
+     *  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
+     * 
+ *

+ * Note: elements are only added to the output collection of the first matching + * predicate, determined by the order of arguments. + *

+ * If the input iterable is null, the same is returned as for an + * empty iterable. + *

+ * 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 the type of object the {@link Iterable} contains + * @param iterable the collection to get the input from, may be null + * @param predicates the predicates to use, may not be null + * @return a list containing the output collections + * @throws NullPointerException if any predicate is null + */ + public static List> partition(final Iterable iterable, + final Predicate... predicates) { + + @SuppressWarnings({ "unchecked", "rawtypes" }) // safe + final Factory> factory = FactoryUtils.instantiateFactory((Class) ArrayList.class); + return partition(iterable, factory, predicates); + } + + /** + * Partitions all elements from iterable into separate output collections, + * based on the evaluation of the given predicates. + *

+ * For each predicate, the returned list will contain a collection holding + * all elements of the input iterable matching the predicate. The last collection + * contained in the list will hold all elements which didn't match any predicate: + *

+     *  [C1, C2, R] = partition(I, P1, P2) with
+     *  I = input
+     *  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
+     * 
+ *

+ * Note: elements are only added to the output collection of the first matching + * predicate, determined by the order of arguments. + *

+ * If the input iterable is null, the same is returned as for an + * empty iterable. + * If no predicates have been provided, all elements of the input collection + * will be added to the rejected collection. + *

+ * 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 the type of object the {@link Iterable} contains + * @param the type of the output {@link Collection} + * @param iterable 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 not be null + * @return a list containing the output collections + * @throws NullPointerException if any predicate is null + */ + public static > List partition(final Iterable iterable, + final Factory partitionFactory, final Predicate... predicates) { + + if (iterable == null) { + final Iterable empty = emptyIterable(); + return partition(empty, partitionFactory, predicates); + } + + if (predicates == null) { + throw new NullPointerException("Predicates must not be null."); + } + + for (Predicate p : predicates) { + if (p == null) { + throw new NullPointerException("Predicate must not be null."); + } + } + + if (predicates.length < 1) { + // return the entire input collection as a single partition + final R singlePartition = partitionFactory.create(); + CollectionUtils.addAll(singlePartition, iterable); + return Collections.singletonList(singlePartition); + } + + // create the empty partitions + final int numberOfPredicates = predicates.length; + final int numberOfPartitions = numberOfPredicates + 1; + final List partitions = new ArrayList(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 : iterable) { + 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; + } + /** * Gets a new list with the contents of the provided iterable. * diff --git a/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java b/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java index 597dd2400..931ea8f2e 100644 --- a/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java +++ b/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java @@ -1070,12 +1070,6 @@ public class CollectionUtilsTest extends MockTestCase { } }; - private static Predicate EVEN = new Predicate() { - public boolean evaluate(final Number input) { - return input.intValue() % 2 == 0; - } - }; - //Up to here @Test public void filter() { @@ -1170,6 +1164,34 @@ public class CollectionUtilsTest extends MockTestCase { assertEquals(2, output2.iterator().next()); } + @Test + public void selectWithOutputCollections() { + List input = new ArrayList(); + input.add(1); + input.add(2); + input.add(3); + input.add(4); + + List output = new ArrayList(); + List rejected = new ArrayList(); + + CollectionUtils.select(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.select((List) null, EQUALS_TWO, output, rejected); + assertTrue(output.isEmpty()); + assertTrue(rejected.isEmpty()); + } + @Test public void selectRejected() { final List list = new ArrayList(); @@ -1189,89 +1211,6 @@ public class CollectionUtilsTest extends MockTestCase { assertTrue(output1.contains(4L)); } - @SuppressWarnings("unchecked") - @Test - public void partition() { - List input = new ArrayList(); - input.add(1); - input.add(2); - input.add(3); - input.add(4); - List> partitions = CollectionUtils.partition(input, EQUALS_TWO); - assertEquals(2, partitions.size()); - - // first partition contains 2 - Collection 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) null, EQUALS_TWO); - assertTrue(partitions.isEmpty()); - - partitions = CollectionUtils.partition(input); - assertEquals(1, partitions.size()); - assertEquals(input, partitions.get(0)); - } - - @Test - public void partitionWithOutputCollections() { - List input = new ArrayList(); - input.add(1); - input.add(2); - input.add(3); - input.add(4); - - List output = new ArrayList(); - List rejected = new ArrayList(); - - 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) null, EQUALS_TWO, output, rejected); - assertTrue(output.isEmpty()); - assertTrue(rejected.isEmpty()); - } - - @Test - public void partitionMultiplePredicates() { - List input = new ArrayList(); - input.add(1); - input.add(2); - input.add(3); - input.add(4); - @SuppressWarnings("unchecked") - List> partitions = CollectionUtils.partition(input, EQUALS_TWO, EVEN); - - // first partition contains 2 - Collection 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 transformer = TransformerUtils.constantTransformer(2L); diff --git a/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java b/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java index 73daa880c..5dea179b6 100644 --- a/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java +++ b/src/test/java/org/apache/commons/collections4/IterableUtilsTest.java @@ -26,6 +26,7 @@ import java.util.LinkedList; import java.util.List; import org.apache.commons.collections4.bag.HashBag; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -89,6 +90,12 @@ public class IterableUtilsTest { } }; + private static Predicate EVEN = new Predicate() { + public boolean evaluate(final Number input) { + return input.intValue() % 2 == 0; + } + }; + // ----------------------------------------------------------------------- @Test public void apply() { @@ -269,6 +276,76 @@ public class IterableUtilsTest { IterableUtils.get(bag, 1); } + @SuppressWarnings("unchecked") + @Test + public void partition() { + List input = new ArrayList(); + input.add(1); + input.add(2); + input.add(3); + input.add(4); + List> partitions = IterableUtils.partition(input, EQUALS_TWO); + assertEquals(2, partitions.size()); + + // first partition contains 2 + Collection 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 = IterableUtils.partition((List) null, EQUALS_TWO); + assertEquals(2, partitions.size()); + assertTrue(partitions.get(0).isEmpty()); + assertTrue(partitions.get(1).isEmpty()); + + partitions = IterableUtils.partition(input); + assertEquals(1, partitions.size()); + assertEquals(input, partitions.get(0)); + + try { + IterableUtils.partition(input, (Predicate) null); + fail("expecting NullPointerException"); + } catch (NullPointerException npe) { + // expected + } + } + + @SuppressWarnings("unchecked") + @Test + public void partitionMultiplePredicates() { + List input = new ArrayList(); + input.add(1); + input.add(2); + input.add(3); + input.add(4); + List> partitions = IterableUtils.partition(input, EQUALS_TWO, EVEN); + + // first partition contains 2 + Collection 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()); + + try { + IterableUtils.partition(input, EQUALS_TWO, null); + } catch (NullPointerException npe) { + // expected + } + } + @Test public void testToString() { String result = IterableUtils.toString(iterableA);