Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Finally. I should really learn to commit more often. I've already spent weeks on this.
  • Loading branch information
seppestas committed Apr 26, 2015
0 parents commit bf127a6
Show file tree
Hide file tree
Showing 8 changed files with 789 additions and 0 deletions.
27 changes: 27 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2012 Seppe Stas. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Seppe Stas nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Go segtree

A segment tree implementation written in Go.

This library allows storing and retrieving elements indexed by a range. It does this by storing the elements in a segment tree.

Based on:
- [go-stree](https://github.com/toberndo/go-stree) by @toberndo
- Chapter 10.3 of [Computational Geometry: Algorithms and Applications](http://www.cs.uu.nl/geobook/) rev. 3 by Mark de Berg, Otfried Cheong, Marc van Kreveld and Mark Overmars (ISBN 978-3-540-77973-5)

The elements are sent to a channel as soon as they are found in the tree. This allows efficient querying of e.g multi-dimensional trees (trees containing trees). The elements are not sent in any specific order, however each found element will only be sent once.

# Example usages:

```go
tree := new(segtree.Tree)
tree.Push(1, 10, "hello, world")
tree.BuildTree()

results, err := tree.QueryIndex(4)
if err != nil {
panic(fmt.Sprintf("Failed to query tree: %s", err.Error()))
}

result := <-results
fmt.Println("Found:", result)
```

An exemplary 2-dimensional segment tree as a tree in a tree:
```go
inner := new(segtree.Tree)
inner.Push(1, 10, "hello, world")
inner.BuildTree()

outer := new(Tree)
outer.Push(0, 99, inner)
outer.BuildTree()

resultsOuter, err := outer.QueryIndex(10)
if err != nil {
panic(fmt.Sprintf("Failed to query outer tree: %s", err.Error()))
}

result := <-resultsOuter
resultsInner, err := result.(*Tree).QueryIndex(4)
if err != nil {
panic(fmt.Sprintf("Failed to query inner tree: %s", err.Error()))
}

result = <-resultsInner
fmt.Println("Found:", result.(string))
```

The library also allows to pretty print the content of the tree for easy debugging.

Example:
```go
tree := new(segtree.Tree)
tree.Push(1, 10, "hello, world")
tree.Push(5, 6, "how are you today?")
tree.Push(9, 45, "test")
tree.BuildTree()

tree.Print()
```

## State of this package

I'm still experimenting with walking the tree concurrently using go routines, but at first sight this does not have any real benefits.
I will commit my benchmarks once I have them properly defined so that performance changes can be tracked consistently.

I'm also still experimenting with "real" multi-dimensional segment trees.

However there are no planned changes to the interface of this package, so it should be safe to use it in your project.
107 changes: 107 additions & 0 deletions examples_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package segtree

import "fmt"

func ExampleTree_QueryIndex() {
tree := new(Tree)
tree.Push(1, 10, "hello, world")
tree.BuildTree()

results, err := tree.QueryIndex(4)
if err != nil {
panic(fmt.Sprintf("Failed to query tree: %s", err.Error()))
}

result := <-results
fmt.Println("Found:", result)

// Output: Found: hello, world
}

func ExampleTree_QueryIndex_multiple_elements() {
tree := new(Tree)
tree.Push(1, 10, "hello, world")
tree.Push(5, 15, 3.14)
tree.BuildTree()

results, err := tree.QueryIndex(6)
if err != nil {
panic(fmt.Sprintf("Failed to query tree: %s", err.Error()))
}

for result := range results {
fmt.Println("Found:", result)
}

// Output:
// Found: hello, world
// Found: 3.14
}

func ExampleTree_QueryIndex_2_dimensional() {
inner := new(Tree)
inner.Push(1, 10, "hello, world")
inner.BuildTree()

outer := new(Tree)
outer.Push(0, 99, inner)
outer.BuildTree()

resultsOuter, err := outer.QueryIndex(10)
if err != nil {
panic(fmt.Sprintf("Failed to query outer tree: %s", err.Error()))
}

result := <-resultsOuter
resultsInner, err := result.(*Tree).QueryIndex(4)
if err != nil {
panic(fmt.Sprintf("Failed to query inner tree: %s", err.Error()))
}

result = <-resultsInner
fmt.Println("Found:", result.(string))

// Output: Found: hello, world
}

func ExampleTree_Clear() {
tree := new(Tree)
tree.Push(1, 10, "hello, world")
tree.BuildTree()

tree.Clear()

_, err := tree.QueryIndex(4)
if err != nil {
// The tree is empty. Trying to Query it will fail
fmt.Println(fmt.Sprintf("Failed to query tree: %s", err.Error()))
}

tree.Push(1, 10, "I destroyed the world")
tree.BuildTree()

results, err := tree.QueryIndex(4)
if err != nil {
panic(fmt.Sprintf("Failed to query tree: %s", err.Error()))
}

result := <-results
// "hello world" will not be found
fmt.Println("Found:", result)

// Output:
// Failed to query tree: Tree is empty. Build the tree first
// Found: I destroyed the world
}

func ExampleTree_Print() {
tree := new(Tree)
tree.Push(1, 10, "hello, world")
tree.Push(5, 6, "how are you today?")
tree.Push(9, 45, "test")
tree.BuildTree()

tree.Print()

// Output is a pretty tree (note that the leafs are not always placed properly)
}
90 changes: 90 additions & 0 deletions print.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package segtree

import "fmt"

func (n *node) print() {
from := fmt.Sprintf("%d", n.segment.from)
switch n.segment.from {
case Inf:
from = "+∞"
case NegInf:
from = "-∞"
}
to := fmt.Sprintf("%d", n.segment.to)
switch n.segment.to {
case Inf:
to = "Inf"
case NegInf:
to = "NegInf"
}
fmt.Printf("(%s,%s)", from, to)
if n.intervals != nil {
fmt.Print("->[")
for _, intrvl := range n.intervals {
fmt.Printf("(%v,%v)=[%v]", intrvl.from, intrvl.to, intrvl.element)
}
fmt.Print("]")
}

}

// Traverse tree recursively call enter when entering node, resp. leave
func traverse(node *node, depth int, enter, leave func(*node, int)) {
if node == nil {
return
}
if enter != nil {
enter(node, depth)
}
traverse(node.left, depth+1, enter, leave)
traverse(node.right, depth+1, enter, leave)
if leave != nil {
leave(node, depth)
}
}

// Returs log with base 2 of an int.
func log2(num int) int {
if num == 0 {
return NegInf
}
i := -1
for num > 0 {
num = num >> 1
i++
}
return i
}

func space(n int) {
for i := 0; i < n; i++ {
fmt.Print(" ")
}
}

// Print prints a binary tree recursively to sdout
func (t *Tree) Print() {
endpoints := len(t.base)*2 + 2
leaves := endpoints*2 - 3
height := 1 + log2(leaves)

fmt.Println("Height:", height, ", leaves:", leaves)
levels := make([][]*node, height+1)

traverse(t.root, 0, func(n *node, depth int) {
levels[depth] = append(levels[depth], n)
}, nil)

for i, level := range levels {
for j, n := range level {
space(12 * (len(levels) - 1 - i))
n.print()
space(1 * (height - i))

if j-1%2 == 0 {
space(2)
}
}
fmt.Println()
}
}
28 changes: 28 additions & 0 deletions print_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package segtree

import (
"testing"
)

func TestLog2(t *testing.T) {
test := func(num, expected int) {
if result := log2(num); result != expected {
t.Errorf("Log₂(%d) should be %d, got %d", num, expected, result)
}
}

// Log₂ of a negative integer is impossible, but instead it returns -1.
// I did not want to bother with NaN errors.
test(-10, -1)
// Log₂ of 0 is indefined, but it's limit aprouches -∞. It returs NegInf,
// which is equal to the minimal value of an integer.
// I did not want to bother with Inf errors.
test(1, 0)
test(2, 1)
test(3, 1)
test(4, 2)
test(9, 3)
test(10, 3)
test(1024, 10)

}
Loading

0 comments on commit bf127a6

Please sign in to comment.