diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a258e8228..9abcc6b30 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -37,6 +37,9 @@ Use the Junit (Jupiter) API #518. LayerManager.Builder implements Supplier. + Add CollectionUtils.duplicateList(Collection). + Add CollectionUtils.duplicateSet(Collection). + Add CollectionUtils.duplicateSequencedSet(Collection). Update bloom filter documentation #508. Bump commons-codec:commons-codec from 1.17.0 to 1.17.1 #514. diff --git a/src/main/java/org/apache/commons/collections4/CollectionUtils.java b/src/main/java/org/apache/commons/collections4/CollectionUtils.java index d6d8d342e..cf7d7c984 100644 --- a/src/main/java/org/apache/commons/collections4/CollectionUtils.java +++ b/src/main/java/org/apache/commons/collections4/CollectionUtils.java @@ -25,6 +25,7 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -756,6 +757,62 @@ public class CollectionUtils { return helper.list(); } + /** + * Finds and returns the List of duplicate elements in the given collection. + * + * @param the type of elements in the collection. + * @param collection the list to test, must not be null. + * @return the set of duplicate elements, may be empty. + * @since 4.5.0-M3 + */ + public static List duplicateList(final Collection collection) { + return new ArrayList<>(duplicateSet(collection)); + } + + /** + * Finds and returns the sequenced Set of duplicate elements in the given collection. + *

+ * Once we are on Java 21 and a new major version, the return type should be SequencedSet. + *

+ * + * @param the type of elements in the collection. + * @param collection the list to test, must not be null. + * @return the set of duplicate elements, may be empty. + * @since 4.5.0-M3 + */ + public static Set duplicateSequencedSet(final Collection collection) { + return duplicateSet(collection, new LinkedHashSet<>()); + } + + /** + * Finds and returns the set of duplicate elements in the given collection. + * + * @param the type of elements in the collection. + * @param collection the list to test, must not be null. + * @return the set of duplicate elements, may be empty. + * @since 4.5.0-M3 + */ + public static Set duplicateSet(final Collection collection) { + return duplicateSet(collection, new HashSet<>()); + } + + /** + * Worker method for {@link #duplicateSet(Collection)} and friends. + * + * @param the type of Collection. + * @param the type of elements in the Collection. + * @param collection the list to test, must not be null. + * @param duplicates the list to test, must not be null. + * @return the set of duplicate elements, may be empty. + */ + static , E> C duplicateSet(final Collection collection, final C duplicates) { + final Set set = new HashSet<>(); + for (final E e : collection) { + (set.contains(e) ? duplicates : set).add(e); + } + return duplicates; + } + /** * Returns the immutable EMPTY_COLLECTION with generic type safety. * diff --git a/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java b/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java index aaecfa991..811396067 100644 --- a/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java +++ b/src/test/java/org/apache/commons/collections4/CollectionUtilsTest.java @@ -26,17 +26,20 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Deque; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -872,6 +875,137 @@ public class CollectionUtilsTest extends MockTestCase { assertThrows(NullPointerException.class, () -> CollectionUtils.disjunction(list, null)); } + @Test + public void testDuplicateListAllSameInList() { + final List input = Arrays.asList(5, 5, 5, 5); + assertEquals(Arrays.asList(5), CollectionUtils.duplicateList(input)); + } + + @Test + public void testDuplicateListEmptyDeque() { + assertTrue(CollectionUtils.duplicateList(new ArrayDeque<>()).isEmpty()); + } + + @Test + public void testDuplicateListEmptyList() { + final List input = Arrays.asList(); + assertTrue(CollectionUtils.duplicateList(input).isEmpty()); + } + + @Test + public void testDuplicateListEmptySet() { + assertTrue(CollectionUtils.duplicateList(new HashSet<>()).isEmpty()); + } + + @Test + public void testDuplicateListMultipleDuplicatesInDeque() { + final Deque input = new ArrayDeque<>(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4)); + final List expected = Arrays.asList(1, 2, 3, 4); + assertEquals(expected, CollectionUtils.duplicateList(input)); + } + + @Test + public void testDuplicateListMultipleDuplicatesInList() { + final List input = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4); + final List expected = Arrays.asList(1, 2, 3, 4); + assertEquals(expected, CollectionUtils.duplicateList(input)); + } + + @Test + public void testDuplicateListNoDuplicates() { + final List input = Arrays.asList(1, 2, 3, 4, 5); + assertTrue(CollectionUtils.duplicateList(input).isEmpty()); + } + + @Test + public void testDuplicateListSingleElement() { + final List input = Arrays.asList(1); + assertTrue(CollectionUtils.duplicateList(input).isEmpty()); + } + + @Test + public void testDuplicateListWithDuplicates() { + final List input = Arrays.asList(1, 2, 3, 2, 4, 5, 3); + final List expected = Arrays.asList(2, 3); + assertEquals(expected, CollectionUtils.duplicateList(input)); + } + + @Test + public void testDuplicateSequencedSetMultipleDuplicates() { + final List input = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4); + final List list = Arrays.asList(1, 2, 3, 4); + assertEquals(list, new ArrayList<>(CollectionUtils.duplicateSequencedSet(input))); + assertEquals(new LinkedHashSet<>(list), CollectionUtils.duplicateSequencedSet(input)); + } + + @Test + public void testDuplicateSetEmptyDeque() { + assertTrue(CollectionUtils.duplicateSet(new ArrayDeque<>()).isEmpty()); + } + + @Test + public void testDuplicateSetEmptyList() { + final List input = Arrays.asList(); + assertTrue(CollectionUtils.duplicateSet(input).isEmpty()); + } + + @Test + public void testDuplicateSetEmptySet() { + assertTrue(CollectionUtils.duplicateSet(new HashSet<>()).isEmpty()); + } + + @Test + public void testDuplicateSetInSet() { + // Sets don't have duplicates, so the result is always an empty set. + final Set input = new HashSet<>(Arrays.asList(5)); + assertTrue(CollectionUtils.duplicateSet(input).isEmpty()); + } + + @Test + public void testDuplicateSetMultipleDuplicatesInDeque() { + final Deque input = new ArrayDeque<>(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4)); + final Set expected = new HashSet<>(Arrays.asList(1, 2, 3, 4)); + assertEquals(expected, CollectionUtils.duplicateSet(input)); + } + + @Test + public void testDuplicateSetMultipleDuplicatesInList() { + final List input = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4); + final Set expected = new HashSet<>(Arrays.asList(1, 2, 3, 4)); + assertEquals(expected, CollectionUtils.duplicateSet(input)); + } + + @Test + public void testDuplicateSetNoDuplicates() { + final List input = Arrays.asList(1, 2, 3, 4, 5); + assertTrue(CollectionUtils.duplicateSet(input).isEmpty()); + } + + @Test + public void testDuplicateSetSingleElement() { + final List input = Arrays.asList(1); + assertTrue(CollectionUtils.duplicateSet(input).isEmpty()); + } + + @Test + public void testDuplicateSetWithDuplicates() { + final List input = Arrays.asList(1, 2, 3, 2, 4, 5, 3); + final Set expected = new HashSet<>(Arrays.asList(2, 3)); + assertEquals(expected, CollectionUtils.duplicateSet(input)); + } + + @Test + public void testDuplicatListAllSameInDeque() { + final Deque input = new ArrayDeque<>(Arrays.asList(5, 5, 5, 5)); + assertEquals(Arrays.asList(5), CollectionUtils.duplicateList(input)); + } + + @Test + public void testDuplicatSetAllSameInDeque() { + final Deque input = new ArrayDeque<>(Arrays.asList(5, 5, 5, 5)); + assertEquals(new HashSet<>(Arrays.asList(5)), CollectionUtils.duplicateSet(input)); + } + @Test public void testEmptyCollection() throws Exception { final Collection coll = CollectionUtils.emptyCollection();