[COLLECTIONS-572] Add set operations to SetUtils.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1686948 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2015-06-22 21:49:27 +00:00
parent 90509ce84e
commit 9314193c8c
3 changed files with 371 additions and 2 deletions

View File

@ -22,6 +22,10 @@
<body>
<release version="4.1" date="TBD" description="">
<action issue="COLLECTIONS-572" dev="tn" type="add">
Added set operations to "SetUtils": union, difference, intersection and disjunction.
The operations return a view of the result that is backed by the input sets.
</action>
<action issue="COLLECTIONS-570" dev="tn" type="update">
All constructors and static factory methods will now throw a "NullPointerException" if
a required input argument is null. Previously sometimes a "IllegalArgumentException" was used.

View File

@ -16,9 +16,12 @@
*/
package org.apache.commons.collections4;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedSet;
@ -68,7 +71,7 @@ public class SetUtils {
*/
@SuppressWarnings("unchecked") // empty set is OK for any type
public static <E> SortedSet<E> emptySortedSet() {
return (SortedSet<E>) EMPTY_SORTED_SET;
return EMPTY_SORTED_SET;
}
/**
@ -419,4 +422,223 @@ public class SetUtils {
return TransformedNavigableSet.transformingNavigableSet(set, transformer);
}
// Set operations
//-----------------------------------------------------------------------
/**
* Returns a unmodifiable <b>view</b> of the union of the given {@link Set}s.
* <p>
* The returned view contains all elements of {@code a} and {@code b}.
*
* @param <E> the generic type that is able to represent the types contained
* in both input sets.
* @param a the first set, must not be null
* @param b the second set, must not be null
* @return a view of the union of the two set
* @throws NullPointerException if either input set is null
* @since 4.1
*/
public static <E> SetView<E> union(final Set<? extends E> a, final Set<? extends E> b) {
if (a == null || b == null) {
throw new NullPointerException("Sets must not be null.");
}
final SetView<E> bMinusA = difference(b, a);
return new SetView<E>() {
@Override
public boolean contains(Object o) {
return a.contains(o) || b.contains(o);
}
@Override
public Iterator<E> createIterator() {
return IteratorUtils.chainedIterator(a.iterator(), bMinusA.iterator());
}
@Override
public boolean isEmpty() {
return a.isEmpty() && b.isEmpty();
}
@Override
public int size() {
return a.size() + bMinusA.size();
}
};
}
/**
* Returns a unmodifiable <b>view</b> containing the difference of the given
* {@link Set}s, denoted by {@code a \ b} (or {@code a - b}).
* <p>
* The returned view contains all elements of {@code a} that are not a member
* of {@code b}.
*
* @param <E> the generic type that is able to represent the types contained
* in both input sets.
* @param a the set to subtract from, must not be null
* @param b the set to subtract, must not be null
* @return a view of the relative complement of of the two sets
* @since 4.1
*/
public static <E> SetView<E> difference(final Set<? extends E> a, final Set<? extends E> b) {
if (a == null || b == null) {
throw new NullPointerException("Sets must not be null.");
}
final Predicate<E> notContainedInB = new Predicate<E>() {
@Override
public boolean evaluate(E object) {
return !b.contains(object);
}
};
return new SetView<E>() {
@Override
public boolean contains(Object o) {
return a.contains(o) && !b.contains(o);
}
@Override
public Iterator<E> createIterator() {
return IteratorUtils.filteredIterator(a.iterator(), notContainedInB);
}
};
}
/**
* Returns a unmodifiable <b>view</b> of the intersection of the given {@link Set}s.
* <p>
* The returned view contains all elements that are members of both input sets
* ({@code a} and {@code b}).
*
* @param <E> the generic type that is able to represent the types contained
* in both input sets.
* @param a the first set, must not be null
* @param b the second set, must not be null
* @return a view of the intersection of the two sets
* @since 4.1
*/
public static <E> SetView<E> intersection(final Set<? extends E> a, final Set<? extends E> b) {
if (a == null || b == null) {
throw new NullPointerException("Sets must not be null.");
}
final Predicate<E> containedInB = new Predicate<E>() {
@Override
public boolean evaluate(E object) {
return b.contains(object);
}
};
return new SetView<E>() {
@Override
public boolean contains(Object o) {
return a.contains(o) && b.contains(o);
}
@Override
public Iterator<E> createIterator() {
return IteratorUtils.filteredIterator(a.iterator(), containedInB);
}
};
}
/**
* Returns a unmodifiable <b>view</b> of the symmetric difference of the given
* {@link Set}s.
* <p>
* The returned view contains all elements of {@code a} and {@code b} that are
* not a member of the other set.
* <p>
* This is equivalent to {@code union(difference(a, b), difference(b, a))}.
*
* @param <E> the generic type that is able to represent the types contained
* in both input sets.
* @param a the first set, must not be null
* @param b the second set, must not be null
* @return a view of the symmetric difference of the two sets
* @since 4.1
*/
public static <E> SetView<E> disjunction(final Set<? extends E> a, final Set<? extends E> b) {
if (a == null || b == null) {
throw new NullPointerException("Sets must not be null.");
}
final SetView<E> aMinusB = difference(a, b);
final SetView<E> bMinusA = difference(b, a);
return new SetView<E>() {
@Override
public boolean contains(Object o) {
return a.contains(o) ^ b.contains(o);
}
@Override
public Iterator<E> createIterator() {
return IteratorUtils.chainedIterator(aMinusB.iterator(), bMinusA.iterator());
}
@Override
public boolean isEmpty() {
return aMinusB.isEmpty() && bMinusA.isEmpty();
}
@Override
public int size() {
return aMinusB.size() + bMinusA.size();
}
};
}
/**
* An unmodifiable <b>view</b> of a set that may be backed by other sets.
* <p>
* If the decorated sets change, this view will change as well. The contents
* of this view can be transferred to another instance via the {@link #copyInto(Set)}
* and {@link #toSet()} methods.
*
* @param <E> the element type
* @since 4.1
*/
public static abstract class SetView<E> extends AbstractSet<E> {
@Override
public Iterator<E> iterator() {
return IteratorUtils.unmodifiableIterator(createIterator());
}
/**
* Return an iterator for this view; the returned iterator is
* not required to be unmodifiable.
* @return a new iterator for this view
*/
protected abstract Iterator<E> createIterator();
@Override
public int size() {
return IteratorUtils.size(iterator());
}
/**
* Copies the contents of this view into the provided set.
*
* @param set the set for copying the contents
*/
public <S extends Set<E>> void copyInto(final S set) {
CollectionUtils.addAll(set, this);
}
/**
* Returns a new set containing the contents of this view.
*
* @return a new set containing all elements of this view
*/
public Set<E> toSet() {
final Set<E> set = new HashSet<E>(size());
copyInto(set);
return set;
}
}
}

View File

@ -16,14 +16,20 @@
*/
package org.apache.commons.collections4;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.collections4.SetUtils.SetView;
import org.apache.commons.collections4.set.PredicatedSet;
import org.junit.Before;
import org.junit.Test;
/**
@ -33,9 +39,32 @@ import org.junit.Test;
*/
public class SetUtilsTest {
private Set<Integer> setA;
private Set<Integer> setB;
@Before
public void setUp() {
setA = new HashSet<Integer>();
setA.add(1);
setA.add(2);
setA.add(3);
setA.add(4);
setA.add(5);
setB = new HashSet<Integer>();
setB.add(3);
setB.add(4);
setB.add(5);
setB.add(6);
setB.add(7);
}
//-----------------------------------------------------------------------
@Test
public void testpredicatedSet() {
final Predicate<Object> predicate = new Predicate<Object>() {
@Override
public boolean evaluate(final Object o) {
return o instanceof String;
}
@ -112,4 +141,118 @@ public class SetUtilsTest {
set.remove(a);
assertEquals(2, set.size());
}
@Test
public void union() {
final SetView<Integer> set = SetUtils.union(setA, setB);
assertEquals(7, set.size());
assertTrue(set.containsAll(setA));
assertTrue(set.containsAll(setB));
final Set<Integer> set2 = SetUtils.union(setA, SetUtils.<Integer>emptySet());
assertEquals(setA, set2);
try {
SetUtils.union(setA, null);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
try {
SetUtils.union(null, setA);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
}
@Test
public void difference() {
final SetView<Integer> set = SetUtils.difference(setA, setB);
assertEquals(2, set.size());
assertTrue(set.contains(1));
assertTrue(set.contains(2));
for (Integer i : setB) {
assertFalse(set.contains(i));
}
final Set<Integer> set2 = SetUtils.difference(setA, SetUtils.<Integer>emptySet());
assertEquals(setA, set2);
try {
SetUtils.difference(setA, null);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
try {
SetUtils.difference(null, setA);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
}
@Test
public void intersection() {
final SetView<Integer> set = SetUtils.intersection(setA, setB);
assertEquals(3, set.size());
assertTrue(set.contains(3));
assertTrue(set.contains(4));
assertTrue(set.contains(5));
assertFalse(set.contains(1));
assertFalse(set.contains(2));
assertFalse(set.contains(6));
assertFalse(set.contains(7));
final Set<Integer> set2 = SetUtils.intersection(setA, SetUtils.<Integer>emptySet());
assertEquals(SetUtils.<Integer>emptySet(), set2);
try {
SetUtils.intersection(setA, null);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
try {
SetUtils.intersection(null, setA);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
}
@Test
public void disjunction() {
final SetView<Integer> set = SetUtils.disjunction(setA, setB);
assertEquals(4, set.size());
assertTrue(set.contains(1));
assertTrue(set.contains(2));
assertTrue(set.contains(6));
assertTrue(set.contains(7));
assertFalse(set.contains(3));
assertFalse(set.contains(4));
assertFalse(set.contains(5));
final Set<Integer> set2 = SetUtils.disjunction(setA, SetUtils.<Integer>emptySet());
assertEquals(setA, set2);
try {
SetUtils.disjunction(setA, null);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
try {
SetUtils.disjunction(null, setA);
fail("Expecting NullPointerException");
} catch (NullPointerException npe) {
// expected
}
}
}