Skip to content

Commit

Permalink
rewrite the segment tree implementation, add varous testings
Browse files Browse the repository at this point in the history
  • Loading branch information
ideahitme committed Jun 8, 2017
1 parent 78fd133 commit 7338e16
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
33 changes: 33 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package segment

import (
"testing"
)

func BenchmarkSegmentTree(b *testing.B) {
s := new(SegmentTreeSuite)
s.SetupTest()

tree := NewTree(s.testdata)

b.ResetTimer()

for i := 0; i < b.N; i++ {
for _, r := range s.ranges {
tree.RangeMinQuery(r[0], r[1])
}
}
}

func BenchmarkNaive(b *testing.B) {
s := new(SegmentTreeSuite)
s.SetupTest()

b.ResetTimer()

for i := 0; i < b.N; i++ {
for _, r := range s.ranges {
s.RMQ(r[0], r[1])
}
}
}
60 changes: 60 additions & 0 deletions suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package segment

import (
"math"
"math/rand"
"testing"
"time"

"github.com/stretchr/testify/suite"
)

type SegmentTreeSuite struct {
suite.Suite
testdata []int
ranges [][]int
}

func (suite *SegmentTreeSuite) SetupTest() {
rand.Seed(time.Now().UnixNano())
size := 1 << 15
numRanges := 1 << 10
maxValue := 1 << 20

testdata := make([]int, size)
for i := 0; i < size; i++ {
testdata[i] = rand.Intn(maxValue)
}
suite.testdata = testdata

ranges := make([][]int, numRanges)
for i := 0; i < numRanges; i++ {
ranges[i] = make([]int, 2)
left := rand.Intn(size - 1)
ranges[i][0] = left
ranges[i][1] = left + rand.Intn(size-left)
}
suite.ranges = ranges
}

//dummy implementation of RMQ
func (suite *SegmentTreeSuite) RMQ(left, right int) int {
res := math.MaxInt32
for i := left; i <= right; i++ {
if res > suite.testdata[i] {
res = suite.testdata[i]
}
}
return res
}

func (suite *SegmentTreeSuite) TestExtensive() {
tree := NewTree(suite.testdata)
for _, r := range suite.ranges {
suite.Equal(suite.RMQ(r[0], r[1]), tree.RangeMinQuery(r[0], r[1]))
}
}

func TestExtensive(t *testing.T) {
suite.Run(t, new(SegmentTreeSuite))
}
75 changes: 75 additions & 0 deletions tree.go
Original file line number Diff line number Diff line change
@@ -1 +1,76 @@
package segment

import (
"math"
)

// Tree implementation of segment tree
type Tree struct {
nodes []int //elements of the tree
size int //size number of elements in the original array
}

// NewTree constructs a segment tree and allows to perform RMQ on provided targetArray
func NewTree(from []int) *Tree {
treeSize := calcTreeSize(len(from))
nodes := make([]int, treeSize)

t := &Tree{nodes, len(from)}
t.build(from, 0, 0, len(from)-1)

return t
}

func (t *Tree) build(from []int, node, leftBound, rightBound int) {
if leftBound == rightBound {
t.nodes[node] = from[leftBound]
return
}

bisect := (leftBound + rightBound) / 2
t.build(from, 2*node+1, leftBound, bisect)
t.build(from, 2*node+2, bisect+1, rightBound)

leftMin := t.nodes[2*node+1]
rightMin := t.nodes[2*node+2]

if leftMin < rightMin {
t.nodes[node] = leftMin
} else {
t.nodes[node] = rightMin
}
}

func calcTreeSize(originalSize int) int {
return 1<<uint(math.Ceil(math.Log2(float64(originalSize)))+1) - 1
}

// RangeMinQuery returns minimum element in the [left,right] slice of the original array
func (t *Tree) RangeMinQuery(left, right int) int {
if left > right {
left, right = right, left
}
return (&query{left: left, right: right, nodes: t.nodes}).rangeMinimum(0, 0, t.size-1)
}

type query struct {
left, right int
nodes []int
}

func (q *query) rangeMinimum(node, leftBound, rightBound int) int {
if q.left > rightBound || q.right < leftBound {
return math.MaxInt32
}
if q.left <= leftBound && q.right >= rightBound {
return q.nodes[node]
}

bisect := (leftBound + rightBound) / 2
leftMin := q.rangeMinimum(2*node+1, leftBound, bisect)
rightMin := q.rangeMinimum(2*node+2, bisect+1, rightBound)
if leftMin < rightMin {
return leftMin
}
return rightMin
}
30 changes: 30 additions & 0 deletions tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package segment

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestCalcTreeSize(t *testing.T) {
assert.Equal(t, 15, calcTreeSize(5), "")
assert.Equal(t, 15, calcTreeSize(8), "")
assert.Equal(t, 3, calcTreeSize(2), "")
assert.Equal(t, 1, calcTreeSize(1), "")
assert.Equal(t, 63, calcTreeSize(32), "")
assert.Equal(t, 127, calcTreeSize(33), "")
}

func TestRangeMinQuery(t *testing.T) {
tree := NewTree([]int{1, 3, 5, 4, 6, 10, 200, -100})

assert.Equal(t, 1, tree.RangeMinQuery(0, 0), "")
assert.Equal(t, 3, tree.RangeMinQuery(1, 1), "")
assert.Equal(t, 5, tree.RangeMinQuery(2, 2), "")
assert.Equal(t, 4, tree.RangeMinQuery(3, 3), "")

assert.Equal(t, -100, tree.RangeMinQuery(1, 7), "")
assert.Equal(t, 3, tree.RangeMinQuery(1, 6), "")
assert.Equal(t, 4, tree.RangeMinQuery(2, 6), "")
assert.Equal(t, -100, tree.RangeMinQuery(7, 7), "")
}

0 comments on commit 7338e16

Please sign in to comment.