Updated rtree library

This commit is contained in:
tidwall 2021-02-04 08:21:08 -07:00
parent 3ed048242e
commit b37e7395a3
5 changed files with 102 additions and 60 deletions

2
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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.

View File

@ -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{},

2
vendor/modules.txt vendored
View File

@ -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