diff --git a/go.mod b/go.mod index e7ec9b07..cb517a23 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/tidwall/gjson v1.6.8 github.com/tidwall/match v1.0.3 github.com/tidwall/pretty v1.0.2 - github.com/tidwall/rbang v1.2.2 + github.com/tidwall/rbang v1.2.3 github.com/tidwall/redbench v0.1.0 github.com/tidwall/redcon v1.4.0 github.com/tidwall/resp v0.1.0 diff --git a/go.sum b/go.sum index a5d78b0f..f6305c9a 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/rbang v1.2.2 h1:j5JZDSsybpGzCabqFpabaQNU5MCmIrlThXVUF7LD99I= github.com/tidwall/rbang v1.2.2/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg= +github.com/tidwall/rbang v1.2.3 h1:Eg48GtzQEqqwU6kxAna0H0G/m41bm/MQl/EIqU7jfK8= +github.com/tidwall/rbang v1.2.3/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg= github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo= github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ= github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA= diff --git a/vendor/github.com/tidwall/rbang/README.md b/vendor/github.com/tidwall/rbang/README.md index 6e493010..4ff26e4c 100644 --- a/vendor/github.com/tidwall/rbang/README.md +++ b/vendor/github.com/tidwall/rbang/README.md @@ -1,4 +1,4 @@ -# `rbang` +# rbang [![GoDoc](https://godoc.org/github.com/tidwall/rbang?status.svg)](https://godoc.org/github.com/tidwall/rbang) @@ -27,7 +27,7 @@ var tr rbang.RTree // insert a point tr.Insert([2]float64{-112.0078, 33.4373}, [2]float64{-112.0078, 33.4373}, "PHX") -// insert a box +// insert a rect tr.Insert([2]float64{10, 10}, [2]float64{20, 20}, "rect") // search @@ -48,11 +48,11 @@ This implementation is a variant of the original paper: ### Inserting -Same as the original algorithm. From the root to the leaf, the boxes which will incur the least enlargment are chosen. Ties go to boxes with the smallest area. +Same as the original algorithm. From the root to the leaf, the rects which will incur the least enlargment are chosen. Ties go to rects with the smallest area. ### Deleting -Same as the original algorithm. A target box is deleted directly. When the number of children in a box falls below it's minumum entries, it is removed from the tree and it's items are re-inserted. +Same as the original algorithm. A target rect is deleted directly. When the number of children in a rect falls below it's minumum entries, it is removed from the tree and it's items are re-inserted. ### Splitting @@ -60,29 +60,19 @@ This is a custom algorithm. It attempts to minimize intensive operations such as pre-sorting the children and comparing overlaps & area sizes. The desire is to do simple single axis distance calculations each child only once, with a target 50/50 chance that the child might be moved in-memory. -When a box has reached it's max number of entries it's largest axis is calculated and the box is split into two smaller boxes, named `left` and `right`. -Each child boxes is then evaluated to determine which smaller box it should be placed into. +When a rect has reached it's max number of entries it's largest axis is calculated and the rect is split into two smaller rects, named `left` and `right`. +Each child rects is then evaluated to determine which smaller rect it should be placed into. Two values, `min-dist` and `max-dist`, are calcuated for each child. - `min-dist` is the distance from the parent's minumum value of it's largest axis to the child's minumum value of the parent largest axis. - `max-dist` is the distance from the parent's maximum value of it's largest axis to the child's maximum value of the parent largest axis. -When the `min-dist` is less than `max-dist` then the child is placed into the `left` box. -When the `max-dist` is less than `min-dist` then the child is placed into the `right` box. +When the `min-dist` is less than `max-dist` then the child is placed into the `left` rect. +When the `max-dist` is less than `min-dist` then the child is placed into the `right` rect. When the `min-dist` is equal to `max-dist` then the child is placed into an `equal` bucket until all of the children are evaluated. -Each `equal` box is then one-by-one placed in either `left` or `right`, whichever has less children. - - -## Performance - -In my testing: - -- Insert show similar performance as the quadratic R-tree and ~1.2x - 1.5x faster than R*tree. -- Search and Delete is ~1.5x - 2x faster than quadratic and about the same as R*tree. - -I hope to provide more details in the future. +Each `equal` rect is then one-by-one placed in either `left` or `right`, whichever has less children. ## License -`rbang` source code is available under the MIT License. +rbang source code is available under the MIT License. diff --git a/vendor/github.com/tidwall/rbang/rbang.go b/vendor/github.com/tidwall/rbang/rbang.go index b19e0956..d9e7ab09 100644 --- a/vendor/github.com/tidwall/rbang/rbang.go +++ b/vendor/github.com/tidwall/rbang/rbang.go @@ -140,18 +140,49 @@ func (tr *RTree) insert(item *rect) { tr.count++ } -func (r *rect) chooseLeastEnlargement(b *rect) int { - j, jenlargement, jarea := -1, 0.0, 0.0 +const inlineEnlargedArea = true + +func (r *rect) chooseLeastEnlargement(b *rect) (index int) { n := r.data.(*node) + j, jenlargement, jarea := -1, 0.0, 0.0 for i := 0; i < n.count; i++ { - area := n.rects[i].area() - enlargement := n.rects[i].enlargedArea(b) - area - if j == -1 || enlargement < jenlargement { - j, jenlargement, jarea = i, enlargement, area - } else if enlargement == jenlargement { - if area < jarea { - j, jenlargement, jarea = i, enlargement, area + var earea float64 + if inlineEnlargedArea { + earea = 1.0 + if b.max[0] > n.rects[i].max[0] { + if b.min[0] < n.rects[i].min[0] { + earea *= b.max[0] - b.min[0] + } else { + earea *= b.max[0] - n.rects[i].min[0] + } + } else { + if b.min[0] < n.rects[i].min[0] { + earea *= n.rects[i].max[0] - b.min[0] + } else { + earea *= n.rects[i].max[0] - n.rects[i].min[0] + } } + if b.max[1] > n.rects[i].max[1] { + if b.min[1] < n.rects[i].min[1] { + earea *= b.max[1] - b.min[1] + } else { + earea *= b.max[1] - n.rects[i].min[1] + } + } else { + if b.min[1] < n.rects[i].min[1] { + earea *= n.rects[i].max[1] - b.min[1] + } else { + earea *= n.rects[i].max[1] - n.rects[i].min[1] + } + } + } else { + earea = n.rects[i].enlargedArea(b) + } + area := n.rects[i].area() + enlargement := earea - area + if j == -1 || enlargement < jenlargement || + (enlargement == jenlargement && area < jarea) { + j, jenlargement, jarea = i, enlargement, area } } return j @@ -233,8 +264,25 @@ func (r *rect) insert(item *rect, height int) (grown bool) { grown = !r.contains(item) return grown } + // choose subtree - index := r.chooseLeastEnlargement(item) + index := -1 + narea := 0.0 + // first take a quick look for any nodes that contain the rect + for i := 0; i < n.count; i++ { + if n.rects[i].contains(item) { + area := n.rects[i].area() + if index == -1 || area < narea { + narea = area + index = i + } + } + } + // found nothing, now go the slow path + if index == -1 { + index = r.chooseLeastEnlargement(item) + } + // insert the item into the child node child := &n.rects[index] grown = child.insert(item, height-1) if grown { @@ -374,8 +422,7 @@ func (tr *RTree) Delete(min, max [2]float64, data interface{}) { return } var removed, recalced bool - removed, recalced, tr.reinsert = - tr.root.delete(&item, tr.height, tr.reinsert[:0]) + removed, recalced = tr.root.delete(tr, &item, tr.height) if !removed { return } @@ -393,60 +440,62 @@ func (tr *RTree) Delete(min, max [2]float64, data interface{}) { if recalced { tr.root.recalc() } - for i := range tr.reinsert { - tr.insert(&tr.reinsert[i]) - tr.reinsert[i].data = nil + if len(tr.reinsert) > 0 { + for i := range tr.reinsert { + tr.insert(&tr.reinsert[i]) + tr.reinsert[i].data = nil + } + tr.reinsert = tr.reinsert[:0] } } -func (r *rect) delete(item *rect, height int, reinsert []rect) ( - removed, recalced bool, reinsertOut []rect, -) { +func (r *rect) delete(tr *RTree, item *rect, height int, +) (removed, recalced bool) { n := r.data.(*node) + rects := n.rects[0:n.count] if height == 0 { - for i := 0; i < n.count; i++ { - if n.rects[i].data == item.data { + for i := 0; i < len(rects); i++ { + if rects[i].data == item.data { // found the target item to delete - recalced = r.onEdge(&n.rects[i]) - n.rects[i] = n.rects[n.count-1] - n.rects[n.count-1].data = nil + recalced = r.onEdge(&rects[i]) + rects[i] = rects[len(rects)-1] + rects[len(rects)-1].data = nil n.count-- if recalced { r.recalc() } - return true, recalced, reinsert + return true, recalced } } } else { - for i := 0; i < n.count; i++ { - if !n.rects[i].contains(item) { + for i := 0; i < len(rects); i++ { + if !rects[i].contains(item) { continue } - removed, recalced, reinsert = - n.rects[i].delete(item, height-1, reinsert) + removed, recalced = rects[i].delete(tr, item, height-1) if !removed { continue } - if n.rects[i].data.(*node).count < minEntries { + if rects[i].data.(*node).count < minEntries { // underflow if !recalced { - recalced = r.onEdge(&n.rects[i]) + recalced = r.onEdge(&rects[i]) } - reinsert = n.rects[i].flatten(reinsert, height-1) - n.rects[i] = n.rects[n.count-1] - n.rects[n.count-1].data = nil + tr.reinsert = rects[i].flatten(tr.reinsert, height-1) + rects[i] = rects[len(rects)-1] + rects[len(rects)-1].data = nil n.count-- } if recalced { r.recalc() } - return removed, recalced, reinsert + return removed, recalced } } - return false, false, reinsert + return false, false } -// flatten flattens all leaf rects into a single list +// flatten all leaf rects into a single list func (r *rect) flatten(all []rect, height int) []rect { n := r.data.(*node) if height == 0 { @@ -525,8 +574,9 @@ func (tr *RTree) Children( return children } -// Replace an item in the structure. This is effectively just a Delete -// followed by an Insert. +// Replace an item. +// This is effectively just a Delete followed by an Insert. Which means the +// new item will always be inserted, whether or not the old item was deleted. func (tr *RTree) Replace( oldMin, oldMax [2]float64, oldData interface{}, newMin, newMax [2]float64, newData interface{}, diff --git a/vendor/modules.txt b/vendor/modules.txt index 66d0d269..6385c329 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -146,7 +146,7 @@ github.com/tidwall/match # github.com/tidwall/pretty v1.0.2 ## explicit github.com/tidwall/pretty -# github.com/tidwall/rbang v1.2.2 +# github.com/tidwall/rbang v1.2.3 ## explicit github.com/tidwall/rbang # github.com/tidwall/redbench v0.1.0