From 7338e16e39742e0683bd9301d0e2733dd9bcd9b8 Mon Sep 17 00:00:00 2001 From: ideahitme Date: Thu, 8 Jun 2017 03:08:04 +0200 Subject: [PATCH] rewrite the segment tree implementation, add varous testings --- bench_test.go | 33 +++++++++++++++++++++++ suite_test.go | 60 +++++++++++++++++++++++++++++++++++++++++ tree.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ tree_test.go | 30 +++++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 bench_test.go create mode 100644 suite_test.go create mode 100644 tree_test.go diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..854cf88 --- /dev/null +++ b/bench_test.go @@ -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]) + } + } +} diff --git a/suite_test.go b/suite_test.go new file mode 100644 index 0000000..47af500 --- /dev/null +++ b/suite_test.go @@ -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)) +} diff --git a/tree.go b/tree.go index e076271..3ebfd04 100644 --- a/tree.go +++ b/tree.go @@ -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< 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 +} diff --git a/tree_test.go b/tree_test.go new file mode 100644 index 0000000..00290b8 --- /dev/null +++ b/tree_test.go @@ -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), "") +}