diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/ArrayContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/ArrayContainer.java index 9858f799e..45cfccfea 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/ArrayContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/ArrayContainer.java @@ -277,6 +277,18 @@ public boolean contains(final short x) { return Util.unsignedBinarySearch(content, 0, cardinality, x) >= 0; } + @Override + public boolean contains(int minimum, int supremum) { + int maximum = supremum - 1; + int start = Util.advanceUntil(content, -1, cardinality, (short)minimum); + int end = Util.advanceUntil(content, start - 1, cardinality, (short)maximum); + return start < cardinality + && end < cardinality + && end - start == maximum - minimum + && content[start] == (short)minimum + && content[end] == (short)maximum; + } + @Override protected boolean contains(RunContainer runContainer) { diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/BitmapContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/BitmapContainer.java index 8f53738e4..eb4f1cdc4 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/BitmapContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/BitmapContainer.java @@ -321,6 +321,29 @@ public boolean contains(final short i) { return (bitmap[x / 64] & (1L << x)) != 0; } + @Override + public boolean contains(int minimum, int supremum) { + int start = minimum >>> 6; + int end = supremum >>> 6; + long first = ~((1L << minimum) - 1); + long last = ((1L << supremum) - 1); + if (start == end) { + return ((bitmap[end] & first & last) == (first & last)); + } + if ((bitmap[start] & first) != first) { + return false; + } + if (end < bitmap.length && (bitmap[end] & last) != last) { + return false; + } + for (int i = start + 1; i < bitmap.length && i < end; ++i) { + if (bitmap[i] != -1L) { + return false; + } + } + return true; + } + @Override protected boolean contains(BitmapContainer bitmapContainer) { if((cardinality != -1) && (bitmapContainer.cardinality != -1)) { diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/Container.java b/roaringbitmap/src/main/java/org/roaringbitmap/Container.java index 4517cb476..f5c84297d 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/Container.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/Container.java @@ -200,6 +200,14 @@ public Container andNot(Container x) { */ public abstract boolean contains(short x); + /** + * Checks whether the container contains the entire range + * @param minimum the inclusive lower bound of the range + * @param supremum the exclusive upper bound of the range + * @return true if the container contains the range + */ + public abstract boolean contains(int minimum, int supremum); + /** * Checks whether the container is a subset of this container or not diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/RoaringBitmap.java b/roaringbitmap/src/main/java/org/roaringbitmap/RoaringBitmap.java index 257a92dc7..163b44c38 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/RoaringBitmap.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/RoaringBitmap.java @@ -1313,6 +1313,47 @@ public boolean contains(final int x) { return c != null && c.contains(Util.lowbits(x)); } + /** + * Checks if the bitmap contains the range. + * @param minimum the inclusive lower bound of the range + * @param supremum the exclusive upper bound of the range + * @return whether the bitmap intersects with the range + */ + public boolean contains(long minimum, long supremum) { + rangeSanityCheck(minimum, supremum); + short firstKey = Util.highbits(minimum); + short lastKey = Util.highbits(supremum); + int span = Util.toIntUnsigned(lastKey) - Util.toIntUnsigned(firstKey); + int len = highLowContainer.size; + if (len < span) { + return false; + } + int begin = highLowContainer.getIndex(firstKey); + int end = highLowContainer.getIndex(lastKey); + end = end < 0 ? -end -1 : end; + if (begin < 0 || end - begin != span) { + return false; + } + + int min = (short)minimum & 0xFFFF; + int sup = (short)supremum & 0xFFFF; + if (firstKey == lastKey) { + return highLowContainer.getContainerAtIndex(begin).contains(min, sup); + } + if (!highLowContainer.getContainerAtIndex(begin).contains(min, 1 << 16)) { + return false; + } + if (end < len && !highLowContainer.getContainerAtIndex(end).contains(0, sup)) { + return false; + } + for (int i = begin + 1; i < end; ++i) { + if (highLowContainer.getContainerAtIndex(i).getCardinality() != 1 << 16) { + return false; + } + } + return true; + } + /** * Deserialize (retrieve) this bitmap. diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/RunContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/RunContainer.java index cef3d339d..3c377d6c7 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/RunContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/RunContainer.java @@ -757,6 +757,25 @@ public boolean contains(short x) { return false; } + @Override + public boolean contains(int minimum, int supremum) { + int count = 0; + for (int i = 0; i < numberOfRuns(); ++i) { + int start = toIntUnsigned(getValue(i)); + int length = toIntUnsigned(getLength(i)); + int stop = start + length; + if (start >= supremum) { + break; + } + if (stop >= supremum) { + count += Math.max(0, supremum - start); + break; + } + count += Math.min(Math.max(0, stop - minimum), length); + } + return count >= supremum - minimum - 1; + } + @Override protected boolean contains(RunContainer runContainer) { int i1 = 0, i2 = 0; diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/ImmutableRoaringBitmap.java b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/ImmutableRoaringBitmap.java index 2716cee52..4562e57f7 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/ImmutableRoaringBitmap.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/ImmutableRoaringBitmap.java @@ -10,14 +10,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import org.roaringbitmap.ImmutableBitmapDataProvider; -import org.roaringbitmap.IntConsumer; -import org.roaringbitmap.IntIterator; -import org.roaringbitmap.PeekableIntIterator; -import org.roaringbitmap.PeekableShortIterator; -import org.roaringbitmap.RoaringBitmap; -import org.roaringbitmap.ShortIterator; -import org.roaringbitmap.Util; +import org.roaringbitmap.*; /** * ImmutableRoaringBitmap provides a compressed immutable (cannot be modified) bitmap. It is meant @@ -995,6 +988,47 @@ public boolean contains(final int x) { && highLowContainer.containsForContainerAtIndex(index, BufferUtil.lowbits(x)); } + /** + * Checks if the bitmap contains the range. + * @param minimum the inclusive lower bound of the range + * @param supremum the exclusive upper bound of the range + * @return whether the bitmap intersects with the range + */ + public boolean contains(long minimum, long supremum) { + MutableRoaringBitmap.rangeSanityCheck(minimum, supremum); + short firstKey = BufferUtil.highbits(minimum); + short lastKey = BufferUtil.highbits(supremum); + int span = BufferUtil.toIntUnsigned(lastKey) - BufferUtil.toIntUnsigned(firstKey); + int len = highLowContainer.size(); + if (len < span) { + return false; + } + int begin = highLowContainer.getIndex(firstKey); + int end = highLowContainer.getIndex(lastKey); + end = end < 0 ? -end -1 : end; + if (begin < 0 || end - begin != span) { + return false; + } + + int min = (short)minimum & 0xFFFF; + int sup = (short)supremum & 0xFFFF; + if (firstKey == lastKey) { + return highLowContainer.getContainerAtIndex(begin).contains(min, sup); + } + if (!highLowContainer.getContainerAtIndex(begin).contains(min, 1 << 16)) { + return false; + } + if (end < len && !highLowContainer.getContainerAtIndex(end).contains(0, sup)) { + return false; + } + for (int i = begin + 1; i < end; ++i) { + if (highLowContainer.getContainerAtIndex(i).getCardinality() != 1 << 16) { + return false; + } + } + return true; + } + /** * Checks whether the parameter is a subset of this RoaringBitmap or not * @param subset the potential subset @@ -1132,7 +1166,6 @@ public boolean intersects(long minimum, long supremum) { .intersects((short) 0, (int)((supremum - 1) & 0xFFFF) + 1); } - /** * Returns the number of distinct integers added to the bitmap (e.g., number of bits set). * diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableArrayContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableArrayContainer.java index ae646b295..f7f950a41 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableArrayContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableArrayContainer.java @@ -1675,6 +1675,18 @@ public boolean intersects(int minimum, int supremum) { return index < cardinality && BufferUtil.toIntUnsigned(content.get(index)) < supremum; } + @Override + public boolean contains(int minimum, int supremum) { + int maximum = supremum - 1; + int start = BufferUtil.advanceUntil(content, -1, cardinality, (short)minimum); + int end = BufferUtil.advanceUntil(content, start - 1, cardinality, (short)maximum); + return start < cardinality + && end < cardinality + && end - start == maximum - minimum + && content.get(start) == (short)minimum + && content.get(end) == (short)maximum; + } + } diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableBitmapContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableBitmapContainer.java index 964af64dc..76fd0db2f 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableBitmapContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableBitmapContainer.java @@ -1981,6 +1981,29 @@ public boolean intersects(int minimum, int supremum) { return false; } + @Override + public boolean contains(int minimum, int supremum) { + int start = minimum >>> 6; + int end = supremum >>> 6; + long first = ~((1L << minimum) - 1); + long last = ((1L << supremum) - 1); + if (start == end) { + return ((bitmap.get(end) & first & last) == (first & last)); + } + if ((bitmap.get(start) & first) != first) { + return false; + } + if (end < bitmap.limit() && (bitmap.get(end) & last) != last) { + return false; + } + for (int i = start + 1; i < bitmap.limit() && i < end; ++i) { + if (bitmap.get(i) != -1L) { + return false; + } + } + return true; + } + @Override protected boolean contains(MappeableRunContainer runContainer) { final int runCardinality = runContainer.getCardinality(); diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableContainer.java index 316914bf9..4a5c74c70 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableContainer.java @@ -223,6 +223,14 @@ public boolean contains(MappeableContainer subset) { * @return true if the container intersects the range */ public abstract boolean intersects(int minimum, int supremum); + + /** + * Checks whether the container contains the entire range + * @param minimum the inclusive lower bound of the range + * @param supremum the exclusive upper bound of the range + * @return true if the container contains the range + */ + public abstract boolean contains(int minimum, int supremum); /** * Fill the least significant 16 bits of the integer array, starting at index index, with the diff --git a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableRunContainer.java b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableRunContainer.java index 4e58a9086..c66f947eb 100644 --- a/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableRunContainer.java +++ b/roaringbitmap/src/main/java/org/roaringbitmap/buffer/MappeableRunContainer.java @@ -2659,13 +2659,32 @@ protected boolean contains(MappeableBitmapContainer bitmapContainer) { public boolean intersects(int minimum, int supremum) { for (int i = 0; i < numberOfRuns(); ++i) { if (BufferUtil.compareUnsigned(getValue(i), (short)minimum) >= 0 - && BufferUtil.toIntUnsigned(getValue(i)) < supremum) { + && toIntUnsigned(getValue(i)) < supremum) { return true; } } return false; } + @Override + public boolean contains(int minimum, int supremum) { + int count = 0; + for (int i = 0; i < numberOfRuns(); ++i) { + int start = toIntUnsigned(getValue(i)); + int length = toIntUnsigned(getLength(i)); + int stop = start + length; + if (start >= supremum) { + break; + } + if (stop >= supremum) { + count += Math.max(0, supremum - start); + break; + } + count += Math.min(Math.max(0, stop - minimum), length); + } + return count >= supremum - minimum - 1; + } + } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/Fuzzer.java b/roaringbitmap/src/test/java/org/roaringbitmap/Fuzzer.java index aa60b7293..86863d42c 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/Fuzzer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/Fuzzer.java @@ -14,6 +14,7 @@ import static java.util.stream.Collectors.toList; import static org.roaringbitmap.RandomisedTestData.randomBitmap; +import static org.roaringbitmap.Util.toUnsignedLong; public class Fuzzer { @@ -221,6 +222,13 @@ public void limitCardinalityXorCardinalityInvariance() { + RoaringBitmap.xorCardinality(rb, rb.limit(rb.getCardinality() / 2))); } + @Test + public void containsRangeFirstLastInvariance() { + verifyInvariance(true, + rb -> RoaringBitmap.add(rb.clone(), toUnsignedLong(rb.first()), toUnsignedLong(rb.last())) + .contains(toUnsignedLong(rb.first()), toUnsignedLong(rb.last()))); + } + @Test public void intersectsRangeFirstLastInvariance() { verifyInvariance(true, rb -> rb.intersects(rb.first(), rb.last())); diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/RandomisedTestData.java b/roaringbitmap/src/test/java/org/roaringbitmap/RandomisedTestData.java index f149cf742..9258d82a8 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/RandomisedTestData.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/RandomisedTestData.java @@ -1,6 +1,8 @@ package org.roaringbitmap; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.IntStream; @@ -81,6 +83,7 @@ public static TestDataSet testCase() { } OrderedWriter writer = new OrderedWriter(); + private List ranges = new ArrayList<>(); public TestDataSet withRunAt(int key) { assert key < 1 << 16; @@ -100,9 +103,19 @@ public TestDataSet withBitmapAt(int key) { return this; } + public TestDataSet withRange(long minimum, long supremum) { + ranges.add(minimum); + ranges.add(supremum); + return this; + } + public RoaringBitmap build() { writer.flush(); - return writer.getUnderlying(); + RoaringBitmap bitmap = writer.getUnderlying(); + for (int i = 0; i < ranges.size(); i += 2) { + bitmap.add(ranges.get(i), ranges.get(i + 1)); + } + return bitmap; } } } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/RoaringBitmapIntervalIntersectionTest.java b/roaringbitmap/src/test/java/org/roaringbitmap/RoaringBitmapIntervalIntersectionTest.java index 2bb3a0d6b..15bde482c 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/RoaringBitmapIntervalIntersectionTest.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/RoaringBitmapIntervalIntersectionTest.java @@ -18,6 +18,10 @@ public static Object[][] params() { {RoaringBitmap.bitmapOf(1 << 31 | 1 << 30), 0, 256}, {RoaringBitmap.bitmapOf(1, 1 << 31 | 1 << 30), 0, 256}, {RoaringBitmap.bitmapOf(1, 1 << 16, 1 << 31 | 1 << 30), 0, 1L << 32}, + {testCase().withArrayAt(10).withBitmapAt(20).withRunAt(30) + .withRange(70000L, 150000L).build(), 70000L, 150000L}, + {testCase().withArrayAt(10).withBitmapAt(20).withRunAt(30) + .withRange(70000L, 150000L).build(), 71000L, 140000L}, {testCase().withArrayAt(0).withBitmapAt(1).withRunAt(20).build(), 67000, 150000}, {testCase().withBitmapAt(0).withArrayAt(1).withRunAt(20).build(), 67000, 150000}, {testCase().withBitmapAt(0).withRunAt(1).withArrayAt(20).build(), 67000, 150000}, @@ -42,9 +46,24 @@ public RoaringBitmapIntervalIntersectionTest(RoaringBitmap bitmap, long minimum, @Test - public void test() { + public void testIntersects() { RoaringBitmap test = new RoaringBitmap(); test.add(minimum, supremum); Assert.assertEquals(RoaringBitmap.intersects(bitmap, test), bitmap.intersects(minimum, supremum)); } + + @Test + public void testContains() { + RoaringBitmap test = new RoaringBitmap(); + test.add(minimum, supremum); + Assert.assertEquals(bitmap.contains(test), bitmap.contains(minimum, supremum)); + Assert.assertTrue(test.contains(minimum, supremum)); + } + + @Test + public void ifContainsThenIntersects() { + boolean contains = bitmap.contains(minimum, supremum); + boolean intersects = bitmap.intersects(minimum, supremum); + Assert.assertTrue(!contains || intersects); + } } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java b/roaringbitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java index 8ae1bcc9d..a5878b76e 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/TestArrayContainer.java @@ -435,6 +435,41 @@ public void testIntersectsWithRange3() { assertFalse(container.intersects(1025, 1 << 16)); } + + @Test + public void testContainsRange() { + Container ac = new ArrayContainer().add(20, 100); + assertFalse(ac.contains(1, 21)); + assertFalse(ac.contains(1, 19)); + assertTrue(ac.contains(20, 100)); + assertTrue(ac.contains(20, 99)); + assertTrue(ac.contains(21, 100)); + assertFalse(ac.contains(21, 101)); + assertFalse(ac.contains(19, 99)); + assertFalse(ac.contains(190, 9999)); + } + + @Test + public void testContainsRange2() { + Container ac = new ArrayContainer() + .add((short)1).add((short)10) + .add(20, 100); + assertFalse(ac.contains(1, 21)); + assertFalse(ac.contains(1, 20)); + assertTrue(ac.contains(1, 2)); + } + + @Test + public void testContainsRangeUnsigned() { + Container ac = new ArrayContainer().add(1 << 15, 1 << 8 | 1 << 15); + assertTrue(ac.contains(1 << 15, 1 << 8 | 1 << 15)); + assertTrue(ac.contains(1 + (1 << 15), (1 << 8 | 1 << 15) - 1)); + assertFalse(ac.contains(1 + (1 << 15), (1 << 8 | 1 << 15) + 1)); + assertFalse(ac.contains((1 << 15) - 1, (1 << 8 | 1 << 15) - 1)); + assertFalse(ac.contains(0, 1 << 15)); + assertFalse(ac.contains(1 << 8 | 1 << 15 | 1, 1 << 16)); + } + private static int lower16Bits(int x) { return ((short)x) & 0xFFFF; } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/TestBitmapContainer.java b/roaringbitmap/src/test/java/org/roaringbitmap/TestBitmapContainer.java index b8772637e..16238af1a 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/TestBitmapContainer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/TestBitmapContainer.java @@ -11,6 +11,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.NoSuchElementException; import java.util.Random; @@ -731,6 +732,54 @@ public void testIntersectsAtEndWord2() { assertFalse(container.intersects(lower16Bits(-499), 1 << 16)); } + @Test + public void testContainsRangeSingleWord() { + long[] bitmap = oddBits(); + bitmap[10] = -1L; + int cardinality = 32 + 1 << 15; + BitmapContainer container = new BitmapContainer(bitmap, cardinality); + assertTrue(container.contains(0, 1)); + assertTrue(container.contains(64 * 10, 64 * 11)); + assertFalse(container.contains(64 * 10, 2 + 64 * 11)); + assertTrue(container.contains(1 + 64 * 10, (64 * 11) - 1)); + } + + @Test + public void testContainsRangeMultiWord() { + long[] bitmap = oddBits(); + bitmap[10] = -1L; + bitmap[11] = -1L; + bitmap[12] |= ((1L << 32) - 1); + int cardinality = 32 + 32 + 16 + 1 << 15; + BitmapContainer container = new BitmapContainer(bitmap, cardinality); + assertTrue(container.contains(0, 1)); + assertFalse(container.contains(64 * 10, (64 * 13) - 30)); + assertTrue(container.contains(64 * 10, (64 * 13) - 31)); + assertTrue(container.contains(1 + 64 * 10, (64 * 13) - 32)); + assertTrue(container.contains(64 * 10, 64 * 12)); + assertFalse(container.contains(64 * 10, 2 + 64 * 13)); + } + + + @Test + public void testContainsRangeSubWord() { + long[] bitmap = oddBits(); + bitmap[bitmap.length - 1] = ~((1L << 63) | 1L); + int cardinality = 32 + 32 + 16 + 1 << 15; + BitmapContainer container = new BitmapContainer(bitmap, cardinality); + assertFalse(container.contains(64 * 1023, 64 * 1024)); + assertFalse(container.contains(64 * 1023, 64 * 1024 - 1)); + assertTrue(container.contains(1 + 64 * 1023, 64 * 1024 - 1)); + assertTrue(container.contains(1 + 64 * 1023, 64 * 1024 - 2)); + assertFalse(container.contains(64 * 1023, 64 * 1023 + 2)); + assertTrue(container.contains(64 * 1023 + 1, 64 * 1023 + 2)); + } + + private static long[] oddBits() { + long[] bitmap = new long[1 << 10]; + Arrays.fill(bitmap, 0x5555555555555555L); + return bitmap; + } private static int lower16Bits(int x) { return ((short)x) & 0xFFFF; diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/TestRoaringBitmap.java b/roaringbitmap/src/test/java/org/roaringbitmap/TestRoaringBitmap.java index cba8fd4db..d57d722d1 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/TestRoaringBitmap.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/TestRoaringBitmap.java @@ -18,6 +18,7 @@ import java.io.*; import java.util.*; +import java.util.stream.IntStream; /** * Generic testing of the roaring bitmaps @@ -4615,4 +4616,49 @@ public void testNegative_last() { Assert.assertEquals(-7, bitmap.last()); } + @Test + public void testContainsRange_ContiguousBitmap() { + RoaringBitmap bitmap = new RoaringBitmap(); + bitmap.add(0L, 1_000_000L); + assertTrue(bitmap.contains(1L, 999_999L)); + assertFalse(bitmap.contains(1L, 1_000_001L)); + bitmap.flip(500_000); + assertFalse(bitmap.contains(1L, 999_999L)); + bitmap.flip(500_000); + bitmap.flip(500_000L, 600_000L); + assertFalse(bitmap.contains(1L, 999_999L)); + assertTrue(bitmap.contains(0L, 500_000L)); + assertFalse(bitmap.contains(2_000_001L, 10_000_000L)); + } + + @Test + public void testContainsRange_SmallBitmap() { + RoaringBitmap bitmap = RoaringBitmap.bitmapOf(1, 2, 3, 4, 5, 6); + assertTrue(bitmap.contains(1, 6)); + assertTrue(bitmap.contains(1, 5)); + assertTrue(bitmap.contains(2, 6)); + assertTrue(bitmap.contains(2, 7)); + assertFalse(bitmap.contains(2, 8)); + assertFalse(bitmap.contains(0, 6)); + assertFalse(bitmap.contains(0, 1)); + assertFalse(bitmap.contains(6, 10)); + assertFalse(bitmap.contains(7, 1 << 16)); + assertFalse(bitmap.contains(1 << 17, 1 << 19)); + } + + @Test + public void testContainsRange_DirtyBitmap() { + OrderedWriter writer = new OrderedWriter(); + IntStream.range(0, 1_000_000) + .map(i -> i * 2) + .forEach(writer::add); + writer.flush(); + RoaringBitmap bitmap = writer.getUnderlying(); + assertFalse(bitmap.contains(0L, 2_000_000L)); + assertFalse(bitmap.contains(0L, 2L)); + assertTrue(bitmap.contains(0L, 1L)); + assertTrue(bitmap.contains(1L << 10, 1| (1L << 10))); + assertFalse(bitmap.contains(1L << 31, 1L << 32)); + } + } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/TestRunContainer.java b/roaringbitmap/src/test/java/org/roaringbitmap/TestRunContainer.java index 1ef37ef12..2b60bab09 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/TestRunContainer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/TestRunContainer.java @@ -3397,6 +3397,46 @@ public void testIntersectsWithRangeManyRuns() { assertTrue(container.intersects(11, 1 << 16)); } + @Test + public void testContainsFull() { + assertTrue(RunContainer.full().contains(0, 1 << 16)); + assertFalse(RunContainer.full().flip((short)(1 << 15)).contains(0, 1 << 16)); + } + + @Test + public void testContainsRange() { + Container rc = new RunContainer().add(1, 100).add(5000, 10000); + assertFalse(rc.contains(0, 100)); + assertFalse(rc.contains(0, 100000)); + assertTrue(rc.contains(1, 100)); + assertTrue(rc.contains(1, 99)); + assertTrue(rc.contains(2, 100)); + assertTrue(rc.contains(5000, 10000)); + assertTrue(rc.contains(5000, 9999)); + assertTrue(rc.contains(5001, 9999)); + assertTrue(rc.contains(5001, 10000)); + assertFalse(rc.contains(100, 5000)); + assertFalse(rc.contains(50, 5000)); + assertFalse(rc.contains(4000, 6000)); + assertFalse(rc.contains(10001, 20000)); + } + + @Test + public void testContainsRange3() { + Container rc = new RunContainer().add(1, 100) + .add(300, 300) + .add(400, 500) + .add(502, 600) + .add(700, 10000); + assertFalse(rc.contains(0, 100)); + assertFalse(rc.contains(500, 600)); + assertFalse(rc.contains(501, 600)); + assertTrue(rc.contains(502, 600)); + assertFalse(rc.contains(600, 700)); + assertTrue(rc.contains(9999, 10000)); + assertFalse(rc.contains(9999, 10001)); + } + private static int lower16Bits(int x) { return ((short)x) & 0xFFFF; } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/BufferFuzzer.java b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/BufferFuzzer.java index f533fb43b..482713a38 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/BufferFuzzer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/BufferFuzzer.java @@ -15,6 +15,8 @@ import java.util.function.Predicate; import java.util.stream.IntStream; +import static org.roaringbitmap.Util.toUnsignedLong; + public class BufferFuzzer { @FunctionalInterface @@ -164,6 +166,13 @@ public void intersectsRangeFirstLastInvariance() { verifyInvariance(true, rb -> rb.intersects(rb.first(), rb.last())); } + @Test + public void containsRangeFirstLastInvariance() { + verifyInvariance(true, + rb -> MutableRoaringBitmap.add(rb.clone(), toUnsignedLong(rb.first()), toUnsignedLong(rb.last())) + .contains(toUnsignedLong(rb.first()), toUnsignedLong(rb.last()))); + } + @Test public void andCardinalityInvariance() { verifyInvariance(100, 1 << 9, diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/RoaringBitmapIntervalIntersectionTest.java b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/RoaringBitmapIntervalIntersectionTest.java index 532b45a7a..5d8f3cc0a 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/RoaringBitmapIntervalIntersectionTest.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/RoaringBitmapIntervalIntersectionTest.java @@ -20,14 +20,18 @@ public static Object[][] params() { {RoaringBitmap.bitmapOf(1 << 31 | 1 << 30), 0, 256}, {RoaringBitmap.bitmapOf(1, 1 << 31 | 1 << 30), 0, 256}, {RoaringBitmap.bitmapOf(1, 1 << 16, 1 << 31 | 1 << 30), 0, 1L << 32}, + {testCase().withArrayAt(10).withBitmapAt(20).withRunAt(30) + .withRange(70000L, 150000L).build(), 70000L, 150000L}, + {testCase().withArrayAt(10).withBitmapAt(20).withRunAt(30) + .withRange(70000L, 150000L).build(), 71000L, 140000L}, {testCase().withArrayAt(0).withBitmapAt(1).withRunAt(20).build(), 67000, 150000}, {testCase().withBitmapAt(0).withArrayAt(1).withRunAt(20).build(), 67000, 150000}, {testCase().withBitmapAt(0).withRunAt(1).withArrayAt(20).build(), 67000, 150000}, {testCase().withArrayAt(0) - .withArrayAt(1) - .withArrayAt(2) - .withBitmapAt(200) - .withRunAt(205).build(), 199 * (1 << 16), 200 * (1 << 16) + (1 << 14)}, + .withArrayAt(1) + .withArrayAt(2) + .withBitmapAt(200) + .withRunAt(205).build(), 199 * (1 << 16), 200 * (1 << 16) + (1 << 14)}, }; } @@ -49,4 +53,26 @@ public void test() { test.add(minimum, supremum); Assert.assertEquals(ImmutableRoaringBitmap.intersects(bitmap, test), bitmap.intersects(minimum, supremum)); } + + @Test + public void testIntersects() { + MutableRoaringBitmap test = new MutableRoaringBitmap(); + test.add(minimum, supremum); + Assert.assertEquals(MutableRoaringBitmap.intersects(bitmap, test), bitmap.intersects(minimum, supremum)); + } + + @Test + public void testContains() { + MutableRoaringBitmap test = new MutableRoaringBitmap(); + test.add(minimum, supremum); + Assert.assertEquals(bitmap.contains(test), bitmap.contains(minimum, supremum)); + Assert.assertTrue(test.contains(minimum, supremum)); + } + + @Test + public void ifContainsThenIntersects() { + boolean contains = bitmap.contains(minimum, supremum); + boolean intersects = bitmap.intersects(minimum, supremum); + Assert.assertTrue(!contains || intersects); + } } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestImmutableRoaringBitmap.java b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestImmutableRoaringBitmap.java index cce9d23ea..36f5a215b 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestImmutableRoaringBitmap.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestImmutableRoaringBitmap.java @@ -7,6 +7,7 @@ import org.junit.Assert; import org.junit.Test; import org.roaringbitmap.IntIterator; +import org.roaringbitmap.OrderedWriter; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MappeableArrayContainer; import org.roaringbitmap.buffer.MappeableBitmapContainer; @@ -15,6 +16,7 @@ import java.io.*; import java.nio.ByteBuffer; import java.util.*; +import java.util.stream.IntStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -1407,4 +1409,50 @@ public void testNegative_last() { Assert.assertEquals(-7, bitmap.last()); } + + @Test + public void testContainsRange_ContiguousBitmap() { + MutableRoaringBitmap bitmap = new MutableRoaringBitmap(); + bitmap.add(0L, 1_000_000L); + assertTrue(bitmap.contains(1L, 999_999L)); + assertFalse(bitmap.contains(1L, 1_000_001L)); + bitmap.flip(500_000); + assertFalse(bitmap.contains(1L, 999_999L)); + bitmap.flip(500_000); + bitmap.flip(500_000L, 600_000L); + assertFalse(bitmap.contains(1L, 999_999L)); + assertTrue(bitmap.contains(0L, 500_000L)); + assertFalse(bitmap.contains(2_000_001L, 10_000_000L)); + } + + @Test + public void testContainsRange_SmallBitmap() { + MutableRoaringBitmap bitmap = MutableRoaringBitmap.bitmapOf(1, 2, 3, 4, 5, 6); + assertTrue(bitmap.contains(1, 6)); + assertTrue(bitmap.contains(1, 5)); + assertTrue(bitmap.contains(2, 6)); + assertTrue(bitmap.contains(2, 7)); + assertFalse(bitmap.contains(2, 8)); + assertFalse(bitmap.contains(0, 6)); + assertFalse(bitmap.contains(0, 1)); + assertFalse(bitmap.contains(6, 10)); + assertFalse(bitmap.contains(7, 1 << 16)); + assertFalse(bitmap.contains(1 << 17, 1 << 19)); + } + + @Test + public void testContainsRange_DirtyBitmap() { + OrderedWriter writer = new OrderedWriter(); + IntStream.range(0, 1_000_000) + .map(i -> i * 2) + .forEach(writer::add); + writer.flush(); + MutableRoaringBitmap bitmap = writer.getUnderlying().toMutableRoaringBitmap(); + assertFalse(bitmap.contains(0L, 2_000_000L)); + assertFalse(bitmap.contains(0L, 2L)); + assertTrue(bitmap.contains(0L, 1L)); + assertTrue(bitmap.contains(1L << 10, 1| (1L << 10))); + assertFalse(bitmap.contains(1L << 31, 1L << 32)); + } + } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java index 4d04142df..af58f56d0 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableArrayContainer.java @@ -379,6 +379,40 @@ public void testIntersectsWithRange3() { assertFalse(container.intersects(1025, 1 << 16)); } + @Test + public void testContainsRange() { + MappeableContainer ac = new MappeableArrayContainer().add(20, 100); + assertFalse(ac.contains(1, 21)); + assertFalse(ac.contains(1, 19)); + assertTrue(ac.contains(20, 100)); + assertTrue(ac.contains(20, 99)); + assertTrue(ac.contains(21, 100)); + assertFalse(ac.contains(21, 101)); + assertFalse(ac.contains(19, 99)); + assertFalse(ac.contains(190, 9999)); + } + + @Test + public void testContainsRange2() { + MappeableContainer ac = new MappeableArrayContainer() + .add((short)1).add((short)10) + .add(20, 100); + assertFalse(ac.contains(1, 21)); + assertFalse(ac.contains(1, 20)); + assertTrue(ac.contains(1, 2)); + } + + @Test + public void testContainsRangeUnsigned() { + MappeableContainer ac = new MappeableArrayContainer().add(1 << 15, 1 << 8 | 1 << 15); + assertTrue(ac.contains(1 << 15, 1 << 8 | 1 << 15)); + assertTrue(ac.contains(1 + (1 << 15), (1 << 8 | 1 << 15) - 1)); + assertFalse(ac.contains(1 + (1 << 15), (1 << 8 | 1 << 15) + 1)); + assertFalse(ac.contains((1 << 15) - 1, (1 << 8 | 1 << 15) - 1)); + assertFalse(ac.contains(0, 1 << 15)); + assertFalse(ac.contains(1 << 8 | 1 << 15 | 1, 1 << 16)); + } + private static int lower16Bits(int x) { return ((short)x) & 0xFFFF; } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableBitmapContainer.java b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableBitmapContainer.java index f28781707..79b60ed4a 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableBitmapContainer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestMappeableBitmapContainer.java @@ -17,6 +17,7 @@ import java.io.ObjectOutputStream; import java.nio.ByteBuffer; import java.nio.LongBuffer; +import java.util.Arrays; import org.junit.Assert; import org.junit.Test; @@ -737,6 +738,55 @@ public void testIntersectsAtEndWord2() { assertFalse(container.intersects(lower16Bits(-499), 1 << 16)); } + @Test + public void testContainsRangeSingleWord() { + long[] bitmap = oddBits(); + bitmap[10] = -1L; + int cardinality = 32 + 1 << 15; + MappeableBitmapContainer container = new MappeableBitmapContainer(LongBuffer.wrap(bitmap), cardinality); + assertTrue(container.contains(0, 1)); + assertTrue(container.contains(64 * 10, 64 * 11)); + assertFalse(container.contains(64 * 10, 2 + 64 * 11)); + assertTrue(container.contains(1 + 64 * 10, (64 * 11) - 1)); + } + + @Test + public void testContainsRangeMultiWord() { + long[] bitmap = oddBits(); + bitmap[10] = -1L; + bitmap[11] = -1L; + bitmap[12] |= ((1L << 32) - 1); + int cardinality = 32 + 32 + 16 + 1 << 15; + MappeableBitmapContainer container = new MappeableBitmapContainer(LongBuffer.wrap(bitmap), cardinality); + assertTrue(container.contains(0, 1)); + assertFalse(container.contains(64 * 10, (64 * 13) - 30)); + assertTrue(container.contains(64 * 10, (64 * 13) - 31)); + assertTrue(container.contains(1 + 64 * 10, (64 * 13) - 32)); + assertTrue(container.contains(64 * 10, 64 * 12)); + assertFalse(container.contains(64 * 10, 2 + 64 * 13)); + } + + + @Test + public void testContainsRangeSubWord() { + long[] bitmap = oddBits(); + bitmap[bitmap.length - 1] = ~((1L << 63) | 1L); + int cardinality = 32 + 32 + 16 + 1 << 15; + MappeableBitmapContainer container = new MappeableBitmapContainer(LongBuffer.wrap(bitmap), cardinality); + assertFalse(container.contains(64 * 1023, 64 * 1024)); + assertFalse(container.contains(64 * 1023, 64 * 1024 - 1)); + assertTrue(container.contains(1 + 64 * 1023, 64 * 1024 - 1)); + assertTrue(container.contains(1 + 64 * 1023, 64 * 1024 - 2)); + assertFalse(container.contains(64 * 1023, 64 * 1023 + 2)); + assertTrue(container.contains(64 * 1023 + 1, 64 * 1023 + 2)); + } + + private static long[] oddBits() { + long[] bitmap = new long[1 << 10]; + Arrays.fill(bitmap, 0x5555555555555555L); + return bitmap; + } + private static int lower16Bits(int x) { return ((short)x) & 0xFFFF; } diff --git a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestRunContainer.java b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestRunContainer.java index 714265957..a495ef438 100644 --- a/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestRunContainer.java +++ b/roaringbitmap/src/test/java/org/roaringbitmap/buffer/TestRunContainer.java @@ -2475,6 +2475,68 @@ public void testIntersectsWithRangeManyRuns() { assertTrue(container.intersects(11, 1 << 16)); } + @Test + public void testContainsFull() { + assertTrue(MappeableRunContainer.full().contains(0, 1 << 16)); + assertFalse(MappeableRunContainer.full().flip((short)(1 << 15)).contains(0, 1 << 16)); + } + + @Test + public void testContainsRange() { + MappeableContainer rc = new MappeableRunContainer().add(1, 100).add(5000, 10000); + assertFalse(rc.contains(0, 100)); + assertFalse(rc.contains(0, 100000)); + assertTrue(rc.contains(1, 100)); + assertTrue(rc.contains(1, 99)); + assertTrue(rc.contains(2, 100)); + assertTrue(rc.contains(5000, 10000)); + assertTrue(rc.contains(5000, 9999)); + assertTrue(rc.contains(5001, 9999)); + assertTrue(rc.contains(5001, 10000)); + assertFalse(rc.contains(100, 5000)); + assertFalse(rc.contains(50, 5000)); + assertFalse(rc.contains(4000, 6000)); + assertFalse(rc.contains(10001, 20000)); + } + + @Test + public void testContainsRange2() { + MappeableContainer rc = new MappeableRunContainer().add(1, 100) + .add(300, 400) + .add(5000, 10000); + assertFalse(rc.contains(0, 100)); + assertFalse(rc.contains(0, 100000)); + assertTrue(rc.contains(1, 100)); + assertTrue(rc.contains(1, 99)); + assertTrue(rc.contains(300, 400)); + assertTrue(rc.contains(2, 100)); + assertTrue(rc.contains(5000, 10000)); + assertTrue(rc.contains(5000, 9999)); + assertTrue(rc.contains(5001, 9999)); + assertTrue(rc.contains(5001, 10000)); + assertFalse(rc.contains(100, 5000)); + assertFalse(rc.contains(50, 5000)); + assertFalse(rc.contains(4000, 6000)); + assertFalse(rc.contains(10001, 20000)); + } + + + @Test + public void testContainsRange3() { + MappeableContainer rc = new MappeableRunContainer().add(1, 100) + .add(300, 300) + .add(400, 500) + .add(502, 600) + .add(700, 10000); + assertFalse(rc.contains(0, 100)); + assertFalse(rc.contains(500, 600)); + assertFalse(rc.contains(501, 600)); + assertTrue(rc.contains(502, 600)); + assertFalse(rc.contains(600, 700)); + assertTrue(rc.contains(9999, 10000)); + assertFalse(rc.contains(9999, 10001)); + } + private static int lower16Bits(int x) { return ((short)x) & 0xFFFF; }