diff --git a/src/changes/changes.xml b/src/changes/changes.xml index f9037565f..560728084 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -298,7 +298,7 @@ Calling "CollectionUtils#sizeIsEmpty(null)" will now return true. - Added methods "CollectionUtils#merge(...)" to merge two sorted Collections + Added methods "CollectionUtils#collate(...)" to merge two sorted Collections into a sorted List using the standard O(n) merge algorithm. diff --git a/src/main/java/org/apache/commons/collections4/CollectionUtils.java b/src/main/java/org/apache/commons/collections4/CollectionUtils.java index 122c898c1..c7520b633 100644 --- a/src/main/java/org/apache/commons/collections4/CollectionUtils.java +++ b/src/main/java/org/apache/commons/collections4/CollectionUtils.java @@ -38,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.CollatingIterator; import org.apache.commons.collections4.iterators.PermutationIterator; /** @@ -1498,9 +1499,9 @@ public class CollectionUtils { * @throws IllegalArgumentException if either collection is null * @since 4.0 */ - public static > List merge(Collection a, - Collection b) { - return merge(a, b, ComparatorUtils.naturalComparator(), true); + public static > List collate(Collection a, + Collection b) { + return collate(a, b, ComparatorUtils.naturalComparator(), true); } /** @@ -1519,10 +1520,10 @@ public class CollectionUtils { * @throws IllegalArgumentException if either collection is null * @since 4.0 */ - public static > List merge(final Collection a, - final Collection b, - final boolean includeDuplicates) { - return merge(a, b, ComparatorUtils.naturalComparator(), includeDuplicates); + public static > List collate(final Collection a, + final Collection b, + final boolean includeDuplicates) { + return collate(a, b, ComparatorUtils.naturalComparator(), includeDuplicates); } /** @@ -1539,9 +1540,9 @@ public class CollectionUtils { * @throws IllegalArgumentException if either collection or the comparator is null * @since 4.0 */ - public static List merge(final Collection a, final Collection b, - final Comparator c) { - return merge(a, b, c, true); + public static List collate(final Collection a, final Collection b, + final Comparator c) { + return collate(a, b, c, true); } /** @@ -1560,8 +1561,8 @@ public class CollectionUtils { * @throws IllegalArgumentException if either collection or the comparator is null * @since 4.0 */ - public static List merge(final Collection a, final Collection b, - final Comparator c, final boolean includeDuplicates) { + public static List collate(final Collection a, final Collection b, + final Comparator c, final boolean includeDuplicates) { if (a == null || b == null) { throw new IllegalArgumentException("The collections must not be null"); @@ -1570,68 +1571,24 @@ public class CollectionUtils { throw new IllegalArgumentException("The comparator must not be null"); } - final List mergedList = new ArrayList(a.size() + b.size()); - - // if either collection is empty, just return the other one - if (a.isEmpty() || b.isEmpty()) { - mergedList.addAll(a.isEmpty() ? b : a); - return mergedList; - } - - final Iterator it1 = a.iterator(); - final Iterator it2 = b.iterator(); - O o1 = it1.next(); - O o2 = it2.next(); - O last = null; - while (true) { - final int x = c.compare(o1, o2); - if (x <= 0) { - last = addItemToList(o1, mergedList, last, includeDuplicates); - if (it1.hasNext()) { - o1 = it1.next(); - } else { - // a is empty, so add current element of b - last = addItemToList(o2, mergedList, last, includeDuplicates); - break; - } - } else { - last = addItemToList(o2, mergedList, last, includeDuplicates); - if (it2.hasNext()) { - o2 = it2.next(); - } else { - // b is empty, so add current element of a - last = addItemToList(o1, mergedList, last, includeDuplicates); - break; - } - } - } - - // add the remaining elements from the non-empty iterator - final Iterator it = it1.hasNext() ? it1 : it2; - while (it.hasNext()) { - last = addItemToList(it.next(), mergedList, last, includeDuplicates); - } - - return mergedList; - } - - /** - * Adds an item to the specified list. - * - * @param item the item to add - * @param list the list to use - * @param lastItem the last added item, may be null - * @param includeDuplicates whether duplicate entries are allowed - * @return the last added item - * @since 4.0 - */ - private static E addItemToList(final E item, final List list, final E lastItem, - final boolean includeDuplicates) { - if (includeDuplicates || lastItem == null || !lastItem.equals(item)) { - list.add(item); - return item; + final int totalSize = Math.max(1, a.size() + b.size()); + final Iterator iterator = new CollatingIterator(c, a.iterator(), b.iterator()); + if (includeDuplicates) { + return IteratorUtils.toList(iterator, totalSize); } else { - return lastItem; + final ArrayList mergedList = new ArrayList(totalSize); + + O lastItem = null; + while (iterator.hasNext()) { + final O item = iterator.next(); + if (lastItem == null || !lastItem.equals(item)) { + mergedList.add(item); + } + lastItem = item; + } + + mergedList.trimToSize(); + return mergedList; } } diff --git a/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java b/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java index 0945ab22f..7c01bbf4f 100644 --- a/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java +++ b/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java @@ -1640,25 +1640,25 @@ public class CollectionUtilsTest extends MockTestCase { } @Test(expected=IllegalArgumentException.class) - public void mergeException1() { - CollectionUtils.merge(collectionA, null); + public void collateException1() { + CollectionUtils.collate(collectionA, null); } @Test(expected=IllegalArgumentException.class) - public void mergeException2() { - CollectionUtils.merge(collectionA, collectionC, null); + public void collateException2() { + CollectionUtils.collate(collectionA, collectionC, null); } @Test - public void testMerge() { - List result = CollectionUtils.merge(emptyCollection, emptyCollection); + public void testCollate() { + List result = CollectionUtils.collate(emptyCollection, emptyCollection); assertEquals("Merge empty with empty", 0, result.size()); - result = CollectionUtils.merge(collectionA, emptyCollection); + result = CollectionUtils.collate(collectionA, emptyCollection); assertEquals("Merge empty with non-empty", collectionA, result); - List result1 = CollectionUtils.merge(collectionD, collectionE); - List result2 = CollectionUtils.merge(collectionE, collectionD); + List result1 = CollectionUtils.collate(collectionD, collectionE); + List result2 = CollectionUtils.collate(collectionE, collectionD); assertEquals("Merge two lists 1", result1, result2); List combinedList = new ArrayList(); @@ -1671,23 +1671,23 @@ public class CollectionUtilsTest extends MockTestCase { final Comparator reverseComparator = ComparatorUtils.reversedComparator(ComparatorUtils.naturalComparator()); - result = CollectionUtils.merge(emptyCollection, emptyCollection, reverseComparator); + result = CollectionUtils.collate(emptyCollection, emptyCollection, reverseComparator); assertEquals("Comparator Merge empty with empty", 0, result.size()); Collections.reverse((List) collectionD); Collections.reverse((List) collectionE); Collections.reverse(combinedList); - result1 = CollectionUtils.merge(collectionD, collectionE, reverseComparator); - result2 = CollectionUtils.merge(collectionE, collectionD, reverseComparator); + result1 = CollectionUtils.collate(collectionD, collectionE, reverseComparator); + result2 = CollectionUtils.collate(collectionE, collectionD, reverseComparator); assertEquals("Comparator Merge two lists 1", result1, result2); assertEquals("Comparator Merge two lists 2", combinedList, result2); } @Test - public void testMergeIgnoreDuplicates() { - List result1 = CollectionUtils.merge(collectionD, collectionE, false); - List result2 = CollectionUtils.merge(collectionE, collectionD, false); + public void testCollateIgnoreDuplicates() { + List result1 = CollectionUtils.collate(collectionD, collectionE, false); + List result2 = CollectionUtils.collate(collectionE, collectionD, false); assertEquals("Merge two lists 1 - ignore duplicates", result1, result2); Set combinedSet = new HashSet();