[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:
parent
90509ce84e
commit
9314193c8c
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue