diff --git a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java index a843584..33a4096 100644 --- a/primitiveCollections/src/main/java/org/lucares/collections/IntList.java +++ b/primitiveCollections/src/main/java/org/lucares/collections/IntList.java @@ -22,7 +22,6 @@ public final class IntList implements Serializable, Cloneable { // TODO support sublists // TODO add lastIndexOf // TODO remove bounds checks that are handled by java, e.g. negative indices - // TODO intersection for unsorted lists private static final long serialVersionUID = 2622570032686034909L; @@ -361,12 +360,19 @@ public final class IntList implements Serializable, Cloneable { *

* This method does not release any memory. Call {@link #trim()} to free unused * memory. + *

+ * For a method that computes the intersection of two lists and also removes + * duplicate values, see {@link #intersection(IntList, IntList)}. + *

+ * The algorithm has a complexity of O(n*log(m)), where n is the length of + * {@code this} and m the length of {@code retain}. * * @param retain * the elements to retain * @throws NullPointerException * if the specified {@link IntList} is null * @see #trim() + * @see #intersection(IntList, IntList) */ public void retainAll(final IntList retain) { @@ -747,8 +753,17 @@ public final class IntList implements Serializable, Cloneable { /** * Returns a list with all elements that are in {@code a} and {@code b}. *

- * The cardinality of each element will be equal to the minimum of the - * cardinality of each element in the given lists. + * The cardinality of each element will be equal to one (like in a set). This + * method returns a new list. The list can have unused capacity. Consider using + * {@link #trim()}. + *

+ * See {@link #retainAll(IntList)} for a method that modifies the list and keeps + * duplicate values. + *

+ * If both lists are sorted, then the complexity is O(n+m), where n is the + * length of the first list and m the length of the second list. If at least one + * list is not sorted, then the complexity is O(n*log(m)), where n is the length + * of the shorter list and m the length of the longer list. * * @param a * a sorted {@link IntList} @@ -758,33 +773,99 @@ public final class IntList implements Serializable, Cloneable { * {@code b} * @throws NullPointerException * if {@code a} or {@code b} is null + * @see #retainAll(IntList) + * @see #trim() */ public static IntList intersection(final IntList a, final IntList b) { - final IntList result = new IntList(Math.min(a.size(), b.size())); + final IntList result; if (a.isSorted() && b.isSorted()) { - int l = 0; - int r = 0; - - while (l < a.size() && r < b.size()) { - - final int lv = a.get(l); - final int rv = b.get(r); - - if (lv < rv) { - l++; - } else if (lv > rv) { - r++; - } else { - result.add(lv); - l++; - r++; - } - } + result = intersectionSorted(a, b); } else { - throw new UnsupportedOperationException("retainAll on unsorted lists is not yet supported"); + result = intersectionUnsorted(a, b); } return result; } + /** + * Implements an intersection algorithm with O(n+m), where n is the length of + * the first list and m the length of the second list. + * + * @param a + * first list + * @param b + * second list + * @return the intersection + */ + private static IntList intersectionSorted(final IntList a, final IntList b) { + final int aSize = a.size(); + final int bSize = b.size(); + final IntList result = new IntList(Math.min(aSize, bSize)); + + assert a.isSorted() : "first list must be sorted"; + assert b.isSorted() : "second list must be sorted"; + + int l = 0; + int r = 0; + + while (l < aSize && r < bSize) { + + final int lv = a.get(l); + final int rv = b.get(r); + + if (lv < rv) { + l++; + } else if (lv > rv) { + r++; + } else { + result.add(lv); + do { + l++; + } while (l < aSize && lv == a.get(l)); + do { + r++; + } while (r < bSize && rv == b.get(r)); + } + } + return result; + } + + /** + * Implements an algorithm with O(n*log(m)), where n is the length of the + * shorter list and m the length of the longer list. + * + * @param a + * first list + * @param b + * second list + * @return the intersection + */ + private static IntList intersectionUnsorted(final IntList a, final IntList b) { + final int aSize = a.size(); + final int bSize = b.size(); + final IntList result; + + if (aSize < bSize) { + result = new IntList(Math.min(aSize, bSize)); + + assert !a.isSorted() || !b.isSorted() : "at least one list must be unsorted"; + + for (int l = 0; l < aSize; l++) { + final int lv = a.get(l); + + if (b.indexOf(lv) >= 0) { + result.add(lv); + } + + while (l + 1 < aSize && lv == a.get(l + 1)) { + l++; + } + } + } else { + result = intersectionUnsorted(b, a); + } + + return result; + } + } diff --git a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java index 47ec989..bb107a3 100644 --- a/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java +++ b/primitiveCollections/src/test/java/org/lucares/collections/IntListTest.java @@ -1094,21 +1094,56 @@ public class IntListTest { Assert.assertEquals(IntList.of(2, 4), actual); } + { + final IntList a = IntList.of(0, 2, 4, 6); + final IntList b = IntList.of(3, 5); + final IntList actual = IntList.intersection(a, b); + Assert.assertEquals(IntList.of(), actual); + } + /* - * cardinality of elements that occur multiple time is equal to the minimum - * cardinality in either list + * duplicate elements are removed in the result */ { final IntList a = IntList.of(3, 3, 3); final IntList b = IntList.of(3, 3); final IntList actual = IntList.intersection(a, b); - Assert.assertEquals(IntList.of(3, 3), actual); + Assert.assertEquals(IntList.of(3), actual); } { - final IntList a = IntList.of(4); + final IntList a = IntList.of(4, 4); final IntList b = IntList.of(4, 4, 4); final IntList actual = IntList.intersection(a, b); Assert.assertEquals(IntList.of(4), actual); } } + + @Test + public void testIntersectionUnsortedLists() { + { + final IntList a = IntList.of(0, 1, 2, 3, 4); + final IntList b = IntList.of(2, 4, 5); + a.shuffle(); + b.shuffle(); + final IntList actual = IntList.intersection(a, b); + actual.sort(); + Assert.assertEquals(IntList.of(2, 4), actual); + } + + /* + * duplicate elements are removed in the result + */ + { + final IntList a = IntList.of(3, 5, 3, 3, 1); + final IntList b = IntList.of(2, 3, 3); + final IntList actual = IntList.intersection(a, b); + Assert.assertEquals(IntList.of(3), actual); + } + { + final IntList a = IntList.of(1, 4); + final IntList b = IntList.of(4, 3, 4, 4, 2); + final IntList actual = IntList.intersection(a, b); + Assert.assertEquals(IntList.of(4), actual); + } + } }