diff --git a/roaring/containers.go b/roaring/containers.go index dc5d4118d..0cd515b9f 100644 --- a/roaring/containers.go +++ b/roaring/containers.go @@ -114,6 +114,13 @@ func (sc *sliceContainers) Clone() Containers { return other } +func (sc *sliceContainers) First() (key uint64, c *Container) { + if len(sc.keys) == 0 { + return 0, nil + } + return sc.keys[0], sc.containers[0] +} + func (sc *sliceContainers) Last() (key uint64, c *Container) { if len(sc.keys) == 0 { return 0, nil diff --git a/roaring/containers_btree.go b/roaring/containers_btree.go index 5934a1244..11f282a83 100644 --- a/roaring/containers_btree.go +++ b/roaring/containers_btree.go @@ -142,6 +142,13 @@ func (btc *bTreeContainers) Clone() Containers { return nbtc } +func (btc *bTreeContainers) First() (key uint64, c *Container) { + if btc.tree.Len() == 0 { + return 0, nil + } + return btc.tree.First() +} + func (btc *bTreeContainers) Last() (key uint64, c *Container) { if btc.tree.Len() == 0 { return 0, nil diff --git a/roaring/containers_test.go b/roaring/containers_test.go index ad95f2f79..6d159b73d 100644 --- a/roaring/containers_test.go +++ b/roaring/containers_test.go @@ -15,6 +15,7 @@ package roaring import ( + "reflect" "testing" ) @@ -98,5 +99,46 @@ func testContainersIterator(cs Containers, t *testing.T) { if itr.Next() { t.Fatalf("itr should be done, but got true") } - +} + +func TestContainers(t *testing.T) { + cs := NewFileBitmap().Containers + first := NewContainerArray([]uint16{1, 2, 3}) + last := NewContainerArray([]uint16{1, 2, 3, 4, 5, 6}) + cs.Put(3, first) + cs.Put(6, last) + + key, container := cs.First() + if key != 3 { + t.Fatalf("cs.First key 3 != %d", key) + } + if !reflect.DeepEqual(first, container) { + t.Fatalf("cs.First container %v != %v", first, container) + } + + key, container = cs.Last() + if key != 6 { + t.Fatalf("cs.First key 6 != %d", key) + } + if !reflect.DeepEqual(last, container) { + t.Fatalf("cs.First container %v != %v", last, container) + } + + cs = NewFileBitmap().Containers + + key, container = cs.First() + if key != 0 { + t.Fatalf("cs.First key 0 != %d", key) + } + if nil != container { + t.Fatalf("cs.First container nil != %v", container) + } + + key, container = cs.Last() + if key != 0 { + t.Fatalf("cs.First key 0 != %d", key) + } + if nil != container { + t.Fatalf("cs.First container nil != %v", container) + } } diff --git a/roaring/roaring.go b/roaring/roaring.go index 2d1a2d08a..73b7e3dc6 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -95,6 +95,9 @@ type Containers interface { // Clone does a deep copy of Containers, including cloning all containers contained. Clone() Containers + // First returns the lowest key and associated container. + First() (key uint64, c *Container) + // Last returns the highest key and associated container. Last() (key uint64, c *Container) @@ -320,6 +323,18 @@ func (b *Bitmap) remove(v uint64) bool { return c.remove(lowbits(v)) } +// Min returns the lowest value in the bitmap. +// Second return value is true if containers exist in the bitmap. +func (b *Bitmap) Min() (uint64, bool) { + if b.Containers.Size() == 0 { + return 0, false + } + + hb, c := b.Containers.First() + lb, ok := c.min() + return hb<<16 | uint64(lb), ok +} + // Max returns the highest value in the bitmap. // Returns zero if the bitmap is empty. func (b *Bitmap) Max() uint64 { @@ -1860,6 +1875,17 @@ func (c *Container) runRemove(v uint16) bool { return true } +// min returns the minimum value in the container. +func (c *Container) min() (uint16, bool) { + if c.isArray() { + return c.arrayMin() + } else if c.isRun() { + return c.runMin() + } else { + return c.bitmapMin() + } +} + // max returns the maximum value in the container. func (c *Container) max() uint16 { if c.isArray() { @@ -1871,6 +1897,15 @@ func (c *Container) max() uint16 { } } +// Second result value is true if array is non-empty. +func (c *Container) arrayMin() (uint16, bool) { + array := c.array() + if len(array) == 0 { + return 0, false + } + return array[0], true +} + func (c *Container) arrayMax() uint16 { array := c.array() if len(array) == 0 { @@ -1879,21 +1914,43 @@ func (c *Container) arrayMax() uint16 { return array[len(array)-1] } +// Second result value is true if array is non-empty. +func (c *Container) bitmapMin() (uint16, bool) { + bitmap := c.bitmap() + for i := 0; i < len(bitmap); i++ { + // If value is zero then skip. + v := bitmap[i] + if v != 0 { + r := bits.TrailingZeros64(v) + return uint16(r + i*64), true + } + } + return 0, false +} + func (c *Container) bitmapMax() uint16 { // Search bitmap in reverse order. bitmap := c.bitmap() - for i := len(bitmap); i > 0; i-- { + for i := len(bitmap) - 1; i > 0; i-- { // If value is zero then skip. - v := bitmap[i-1] + v := bitmap[i] if v != 0 { r := bits.LeadingZeros64(v) - return uint16((i-1)*64 + 63 - r) + return uint16(i*64 + 63 - r) } - } return 0 } +// Second result value is true if array is non-empty. +func (c *Container) runMin() (uint16, bool) { + runs := c.runs() + if len(runs) == 0 { + return 0, false + } + return runs[0].start, true +} + func (c *Container) runMax() uint16 { runs := c.runs() if len(runs) == 0 { diff --git a/roaring/roaring_test.go b/roaring/roaring_test.go index b4f792629..210ffa94d 100644 --- a/roaring/roaring_test.go +++ b/roaring/roaring_test.go @@ -309,6 +309,32 @@ func TestBitmap_Max(t *testing.T) { } } +// Ensure bitmap can return the lowest value. +func TestBitmap_Min(t *testing.T) { + bm := roaring.NewFileBitmap() + for i := uint64(100000); i > 0; i-- { + if _, err := bm.Add(i); err != nil { + t.Fatalf("adding bits: %v", err) + } + + v, ok := bm.Min() + if !ok { + t.Fatalf("ok should be true") + } + + if v != i { + t.Fatalf("min: got=%d; want=%d", v, i) + } + } + + // empty bitmap + bm = roaring.NewFileBitmap() + _, ok := bm.Min() + if ok { + t.Fatalf("ok should be false") + } +} + // Ensure CountRange is correct even if rangekey is prior to initial container. func TestBitmap_BitmapCountRangeEdgeCase(t *testing.T) { s := uint64(2009 * pilosa.ShardWidth)