From 2b0cd2f234aa345a1b64bbe9f85538abd909f21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Podg=C3=B3rski?= Date: Sat, 6 Jun 2020 01:02:23 +0200 Subject: [PATCH] Add unionRunRunInPlace --- roaring/roaring.go | 142 ++++++++++++++++- roaring/roaring_internal_test.go | 262 +++++++++++++++++++++++++++++++ 2 files changed, 402 insertions(+), 2 deletions(-) diff --git a/roaring/roaring.go b/roaring/roaring.go index cbb7dc583..dd13a99de 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -3134,8 +3134,7 @@ func (c *Container) unionInPlace(other *Container) *Container { c = c.runToBitmap() return unionBitmapArrayInPlace(c, other) case containerRun: - c = c.runToBitmap() - return unionBitmapRunInPlace(c, other) + return unionRunRunInPlace(c, other) } } if roaringParanoia { @@ -4715,6 +4714,145 @@ func unionBitmapBitmapInPlace(a, b *Container) *Container { return a } +// unions run b into run a, mutating a in place. +func unionRunRunInPlace(a, b *Container) *Container { + statsHit("unionInPlace/RunRun") + + a = a.Thaw() + runs, n := unionInterval16InPlace(a.runs(), b.runs()) + + a.setN(n) + a.setRuns(runs) + return a +} + +func unionInterval16InPlace(a, b []interval16) ([]interval16, int32) { + n := int32(0) + + an, bn := len(a), len(b) + i, j, k := 0, 0, 0 + for { + // select the next interval - v + var ( + v *interval16 + local *interval16 + visa bool // v is from a + ) + if i < an && j < bn { + if a[i].start <= b[j].start { + v, visa = &a[i], true + i++ + } else if j < bn { + v = &b[j] + j++ + } + } else { + if i < an { + v, visa = &a[i], true + i++ + } else if j < bn { + v = &b[j] + j++ + } + } + // no more intervals - break + if v == nil { + if len(a) > 0 { + // count the last interval + n += int32(a[k].last-a[k].start) + 1 + } + break + } + + next: + if k == an { + // append what's left + a = append(a, *v) + an++ + continue + } + + // current, locally unioned interval + if local == nil { + local = &a[k] + } + + if v.last < local.start { + // [---- v ----][---- local ----] + // insert v into a + a = append(a, interval16{}) + copy(a[i+1:], a[i:]) + a[i] = *v + an++ + i++ + continue + } + + if v.start > local.last { + // [---- local ----][---- v ----] + // set already unioned interval and go next + // if int32(v.start-local.last) <= 1 { + // local.last = v.last + // continue + // } + + a[k] = *local + n += int32(local.last-local.start) + 1 + k++ + + // if we set unioned interval we can go to the next interval + if visa { + // v is from a, so we can skip "digested intervals" + // and jump already to v + local = v + } else { + // ...otherwise, move to the next one, + // so let assign local in next iteration + local = nil + } + + goto next + } + + if v.start < local.start { + if v.last >= local.start && v.last <= local.last { + // [---- local ----] + // [---- v ----] + local.start = v.start + } else if v.last > local.last { + // [- local -] + // [---- v ----] + *local = *v + } + continue + } + + if v.start >= local.start && v.start <= local.last { + if v.last <= local.last { + // [---- local ----] + // [-- v --] + + // this assignment looks silly, but if we extended local + // and "digested" many nested intervals, we can squash a, e.g.: + // [a1] [a2] [a3] [a4] + // [------ b ------] + // will give us: + // [ ----- a1 -----] [a4] + a[k] = *local + } else if v.last > local.last { + // [---- local ----] + // [---- v ----] + local.last = v.last + } + } + } + + if len(a) > 0 { + a = a[:k+1] + } + return a, n +} + func difference(a, b *Container) *Container { if a.N() == 0 || b.N() == maxContainerVal+1 { return nil diff --git a/roaring/roaring_internal_test.go b/roaring/roaring_internal_test.go index 3a0749cc8..665ac9857 100644 --- a/roaring/roaring_internal_test.go +++ b/roaring/roaring_internal_test.go @@ -721,6 +721,268 @@ func TestUnionMixed(t *testing.T) { }) } +func TestUnionInterval16InPlace(t *testing.T) { + t.Run("single overlap", func(t *testing.T) { + a := []interval16{interval16{1, 10}, interval16{21, 28}} + b := []interval16{interval16{8, 12}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 20 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 12}, interval16{21, 28}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("nested intervals", func(t *testing.T) { + a := []interval16{interval16{3, 13}, interval16{17, 20}} + b := []interval16{interval16{1, 4}, interval16{6, 7}, interval16{8, 9}, interval16{10, 11}, interval16{14, 17}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 20 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 13}, interval16{14, 20}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("no overlap", func(t *testing.T) { + a := []interval16{interval16{3, 4}, interval16{7, 8}} + b := []interval16{interval16{1, 2}, interval16{5, 6}, interval16{9, 10}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 10 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 6}, interval16{7, 8}, interval16{9, 10}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("b in a", func(t *testing.T) { + a := []interval16{interval16{1, 10}} + b := []interval16{interval16{5, 7}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 10 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 10}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("a eq b", func(t *testing.T) { + a := []interval16{interval16{1, 10}} + b := []interval16{interval16{1, 10}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 10 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 10}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("a in b", func(t *testing.T) { + a := []interval16{interval16{5, 7}} + b := []interval16{interval16{1, 10}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 10 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 10}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("a ahead b", func(t *testing.T) { + a := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 7}} + b := []interval16{interval16{10, 11}, interval16{12, 13}, interval16{14, 15}} + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 13 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{ + interval16{1, 2}, + interval16{3, 4}, + interval16{5, 7}, + interval16{10, 11}, + interval16{12, 13}, + interval16{14, 15}, + } + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("b ahead a", func(t *testing.T) { + a := []interval16{interval16{10, 11}, interval16{12, 13}, interval16{14, 15}} + b := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 7}} + + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 13 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{ + interval16{1, 2}, + interval16{3, 4}, + interval16{5, 7}, + interval16{10, 11}, + interval16{12, 13}, + interval16{14, 15}, + } + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("empty a and b", func(t *testing.T) { + a := []interval16{} + b := []interval16{} + + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 0 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("empty a", func(t *testing.T) { + a := []interval16{} + b := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 7}} + + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 7 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 7}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("empty b", func(t *testing.T) { + a := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 7}} + b := []interval16{} + + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 7 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 2}, interval16{3, 4}, interval16{5, 7}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("single a", func(t *testing.T) { + a := []interval16{interval16{1, 2}} + b := []interval16{} + + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 2 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 2}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("single b", func(t *testing.T) { + a := []interval16{} + b := []interval16{interval16{1, 2}} + + runs, n := unionInterval16InPlace(a, b) + + const expectedN = 2 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + expected := []interval16{interval16{1, 2}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + }) + + t.Run("oddBitsSet lastBitUnset", func(t *testing.T) { + a := []interval16{interval16{1, 1}, interval16{3, 3}, interval16{5, 5}} + b := []interval16{interval16{0, 4}} + + runs, n := unionInterval16InPlace(a, b) + + expected := []interval16{interval16{0, 4}, interval16{5, 5}} + for i, v := range expected { + if runs[i] != v { + t.Fatalf("runs expected: %+v, got: %+v", expected, runs) + } + } + const expectedN = 6 + if n != expectedN { + t.Fatalf("N expected: %d, got: %d", expectedN, n) + } + + }) +} + func TestIntersectMixed(t *testing.T) { a := NewContainerRun([]interval16{{start: 5, last: 10}}) b := NewContainerArray([]uint16{1, 4, 5, 7, 10, 11, 12})