segment_tree 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  class SegmentTree
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/segment_tree.rb CHANGED
@@ -12,73 +12,24 @@
12
12
  # ip_tree = SegmentTree.new(data)
13
13
  #
14
14
  # client_ip = IPAddr.new("87.224.241.66")
15
- # ip_tree.find_first(client_ip).value # => {:city=>"YEKT"}
15
+ # ip_tree.find(client_ip).value # => {:city=>"YEKT"}
16
16
  class SegmentTree
17
- # An abstract tree node
18
- class Node #:nodoc:all:
19
- attr_reader :begin, :end
20
-
21
- def initialize(*)
22
- @begin, @end = @range.begin, @range.end
23
- end
24
- protected :initialize
25
-
26
- def include?(x)
27
- @range.include?(x)
28
- end
29
- end
30
-
31
- # An elementary intervals or nodes container
32
- class Container < Node #:nodoc:all:
33
- extend Forwardable
34
-
35
- #attr_reader :left, :right
36
-
37
- # Node constructor, accepts both +Node+ and +Segment+
38
- def initialize(left, right)
39
- @left, @right = left, right
40
- @range = left.begin..(right || left).end
41
-
42
- super
43
- end
44
-
45
- # Find all intervals containing point +x+ within node's children. Returns array
46
- def find(x)
47
- [@left, @right].compact.
48
- select { |node| node.include?(x) }.
49
- map { |node| node.find(x) }.
50
- flatten
51
- end
52
-
53
- # Find first interval containing point +x+ within node's children
54
- def find_first(x)
55
- subset = [@left, @right].compact.find { |node| node.include?(x) }
56
- subset && subset.find_first(x)
57
- end
58
-
59
- # Do not expose left and right, otherwise output shall be too long on large trees
60
- def inspect
61
- "#<#{self.class.name}:0x#{object_id.to_s(16)} @range=#{@range.inspect}>"
62
- end
63
- end
64
-
65
17
  # An elementary interval
66
- class Segment < Node #:nodoc:all:
67
- attr_reader :value
18
+ class Segment #:nodoc:all:
19
+ attr_reader :range, :value
68
20
 
69
21
  def initialize(range, value)
70
22
  raise ArgumentError, 'Range expected, %s given' % range.class.name unless range.is_a?(Range)
71
23
 
72
24
  @range, @value = range, value
73
- super
74
- end
75
-
76
- def find(x)
77
- [find_first(x)].compact
78
25
  end
79
26
 
80
- def find_first(x)
81
- @range.include?(x) ? self : nil
27
+ # segments are sorted from left to right, from shortest to longest
28
+ def <=>(other)
29
+ case cmp = @range.begin <=> other.range.begin
30
+ when 0 then @range.end <=> other.range.end
31
+ else cmp
32
+ end
82
33
  end
83
34
  end
84
35
 
@@ -90,37 +41,60 @@ class SegmentTree
90
41
  # 2. 2-dimensional array - an array of arrays where first element of
91
42
  # each element is range, and second is value:
92
43
  # <code>[[(0..3), some_value1], [(4..6), some_value2] ...]<code>
93
- def initialize(data)
44
+ #
45
+ # You can pass optional argument +sorted+.
46
+ # If +sorted+ is true then tree consider that data already sorted in proper order.
47
+ # Use it at your own risk!
48
+ def initialize(data, sorted = false)
94
49
  # build elementary segments
95
- nodes = case data
50
+ @segments = case data
96
51
  when Hash, Array, Enumerable then
97
52
  data.collect { |range, value| Segment.new(range, value) }
98
53
  else raise ArgumentError, '2-dim Array or Hash expected'
99
- end.sort! do |x, y|
100
- # intervals are sorted from left to right, from shortest to longest
101
- x.begin == y.begin ?
102
- x.end <=> y.end :
103
- x.begin <=> y.begin
104
- end
105
-
106
- # now build binary tree
107
- while nodes.length > 1
108
- nodes = nodes.each_slice(2).collect { |left, right| Container.new(left, right) }
109
54
  end
110
55
 
111
- # root node is first node or nil when tree is empty
112
- @root = nodes.first
56
+ @segments.sort! unless sorted
113
57
  end
114
58
 
115
- # Find all intervals containing point +x+
116
- # @return [Array]
59
+ # Find first interval containing point +x+.
60
+ # @return [Segment|NilClass]
117
61
  def find(x)
118
- @root ? @root.find(x) : []
62
+ return nil if x.nil?
63
+ low = 0
64
+ high = @segments.size - 1
65
+ while low <= high
66
+ mid = (low + high) / 2
67
+
68
+ case matches?(x, low, high, mid)
69
+ when -1 then high = mid - 1
70
+ when 1 then low = mid + 1
71
+ when 0 then return @segments[mid]
72
+ else return nil
73
+ end
74
+ end
75
+ nil
119
76
  end
120
77
 
121
- # Find first interval containing point +x+.
122
- # @return [Segment|NilClass]
123
- def find_first(x)
124
- @root && @root.find_first(x)
78
+ def inspect
79
+ if @segments.size > 0
80
+ "SegmentTree(#{@segments.first.range.begin}..#{@segments.last.range.end})"
81
+ else
82
+ "SegmentTree(empty)"
83
+ end
84
+ end
85
+
86
+ private
87
+ def matches?(x, low_idx, high_idx, idx) #:nodoc:
88
+ low, high = @segments[low_idx], @segments[high_idx]
89
+ segment = @segments[idx]
90
+ left = idx > 0 && @segments[idx - 1]
91
+ right = idx < @segments.size - 1 && @segments[idx + 1]
92
+
93
+ case
94
+ when left && (low.range.begin..left.range.end).include?(x) then -1
95
+ when segment.range.include?(x) then 0
96
+ when right && (right.range.begin..high.range.end).include?(x) then 1
97
+ else nil
98
+ end
125
99
  end
126
100
  end
@@ -1,32 +1,63 @@
1
1
  require "spec_helper"
2
2
  require "segment_tree"
3
3
 
4
+ # subject { tree }
5
+ # it { should query(12).and_return("b") }
6
+ RSpec::Matchers.define :query do |key|
7
+ chain :and_return do |expected|
8
+ @expected = expected
9
+ @expected = nil if @expected == :nothing
10
+ end
11
+
12
+ match do |tree|
13
+ result = tree.find(key)
14
+ result &&= result.value
15
+
16
+ result.should eq @expected
17
+ end
18
+
19
+ failure_message_for_should do |tree|
20
+ result = tree.find(key)
21
+ result &&= result.value
22
+
23
+ "expected that #{tree.inspect} would return #{@expected.inspect} when querying #{key.inspect}, " +
24
+ "but #{result.inspect} returned instead"
25
+ end
26
+ end
27
+
4
28
  describe SegmentTree do
5
29
  # some fixtures
6
30
  # [[0..9, "a"], [10..19, "b"], ..., [90..99, "j"]] - spanned intervals
7
- let(:sample_spanned) { (0..9).zip("a".."j").map { |num, letter| [(num * 10)..(num + 1) * 10 - 1, letter] } }
8
- # [[0..12, "a"], [10..22, "b"], ..., [90..102, "j"]] - partially overlapping intervals
9
- let(:sample_overlapping) { (0..9).zip("a".."j").map { |num, letter| [(num * 10)..(num + 1) * 10 + 2, letter] } }
31
+ let(:sample_spanned) { (0..9).zip("a".."j").map { |num, letter| [(num * 10)..(num + 1) * 10 - 1, letter] }.shuffle }
32
+ # [[0..10, "a"], [10..20, "b"], ..., [90..100, "j"]] - partially overlapping intervals
33
+ let(:sample_overlapping) { (0..9).zip("a".."j").map { |num, letter| [(num * 10)..(num + 1) * 10 + 2, letter] }.shuffle }
10
34
  # [[0..5, "a"], [10..15, "b"], ..., [90..95, "j"]] - sparsed intervals
11
- let(:sample_sparsed) { (0..9).zip("a".."j").map { |num, letter| [(num * 10)..(num + 1) * 10 - 5, letter] } }
35
+ let(:sample_sparsed) { (0..9).zip("a".."j").map { |num, letter| [(num * 10)..(num + 1) * 10 - 5, letter] }.shuffle }
36
+
37
+ # [[0..5, "a"], [0..7, "aa"], [10..15, "b"], [10..17, "bb"], ..., [90..97, "jj"]]
38
+ let(:sample_overlapping2) do
39
+ (0..9).zip("a".."j").map do |num, letter|
40
+ [(num * 10)..(num + 1) * 10 - 5, letter,
41
+ (num * 10)..(num + 1) * 10 - 3, letter * 2]
42
+ end.
43
+ flatten.
44
+ each_slice(2).
45
+ to_a.
46
+ shuffle
47
+ end
12
48
 
13
49
  describe ".new" do
14
50
  context "given a hash with ranges as keys" do
15
51
  let :data do
16
- {0..3 => "a",
52
+ {7..9 => "a",
17
53
  4..6 => "b",
18
- 7..9 => "c",
54
+ 0..3 => "c",
19
55
  10..12 => "d"}
20
56
  end
21
57
 
22
58
  subject(:tree) { SegmentTree.new(data) }
23
59
 
24
60
  it { should be_a SegmentTree }
25
-
26
- it "should have a root" do
27
- root = tree.instance_variable_get :@root
28
- root.should be_a SegmentTree::Container
29
- end
30
61
  end
31
62
 
32
63
  context "given an array of arrays" do
@@ -34,17 +65,26 @@ describe SegmentTree do
34
65
  [[0..3, "a"],
35
66
  [4..6, "b"],
36
67
  [7..9, "c"],
37
- [10..12, "d"]]
68
+ [10..12, "d"]].shuffle
38
69
  end
39
70
 
40
71
  subject(:tree) { SegmentTree.new(data) }
41
72
 
42
73
  it { should be_a SegmentTree }
74
+ end
43
75
 
44
- it "should have a root" do
45
- root = tree.instance_variable_get :@root
46
- root.should be_a SegmentTree::Container
76
+ context "given preordered data" do
77
+ let :data do
78
+ [[0..3, "a"],
79
+ [4..6, "b"],
80
+ [7..9, "c"],
81
+ [10..12, "d"]]
47
82
  end
83
+
84
+ subject(:tree) { SegmentTree.new(data, true) }
85
+
86
+ it { should be_a SegmentTree }
87
+ it { should query(8).and_return("c") }
48
88
  end
49
89
 
50
90
  context "given nor hash neither array" do
@@ -63,107 +103,32 @@ describe SegmentTree do
63
103
  end
64
104
  end
65
105
 
66
- describe "#find" do
106
+ describe "querying" do
67
107
  context "given spanned intervals" do
68
- let(:tree) { SegmentTree.new(sample_spanned) }
108
+ subject { SegmentTree.new(sample_spanned) }
69
109
 
70
- context "and looking up for existent point" do
71
- subject { tree.find 12 }
72
-
73
- it { should be_a Array }
74
- it { should have_exactly(1).item }
75
- its(:first) { should be_a SegmentTree::Segment }
76
- its('first.value') { should eq 'b' }
77
- end
78
-
79
- context "and looking up for non-existent point" do
80
- subject { tree.find 101 }
81
-
82
- it { should be_a Array }
83
- it { should be_empty }
84
- end
110
+ it { should query(12).and_return('b') }
111
+ it { should query(101).and_return(:nothing) }
85
112
  end
86
113
 
87
114
  context "given partially overlapping intervals" do
88
- let(:tree) { SegmentTree.new(sample_overlapping) }
89
-
90
- context "and looking up for existent point" do
91
- subject { tree.find 11 }
115
+ subject { SegmentTree.new(sample_overlapping) }
92
116
 
93
- it { should be_a Array }
94
- it { should have_exactly(2).item }
95
- its(:first) { should be_a SegmentTree::Segment }
96
- its('first.value') { should eq 'a' }
97
- its(:last) { should be_a SegmentTree::Segment }
98
- its('last.value') { should eq 'b' }
99
- end
117
+ it { should query(11).and_return('a') }
100
118
  end
101
119
 
102
120
  context "given sparsed intervals" do
103
- let(:tree) { SegmentTree.new(sample_sparsed) }
121
+ subject { SegmentTree.new(sample_sparsed) }
104
122
 
105
- context "and looking up for existent point" do
106
- subject { tree.find 12 }
107
-
108
- it { should be_a Array }
109
- it { should have_exactly(1).item }
110
- its(:first) { should be_a SegmentTree::Segment }
111
- its('first.value') { should eq 'b' }
112
- end
113
-
114
- context "and looking up for non-existent point" do
115
- subject { tree.find 8 }
116
-
117
- it { should be_a Array }
118
- it { should be_empty }
119
- end
123
+ it { should query(12).and_return('b') }
124
+ it { should query(8).and_return(:nothing) }
120
125
  end
121
- end
122
-
123
- describe "#find_first" do
124
- context "given spanned intervals" do
125
- let(:tree) { SegmentTree.new(sample_spanned) }
126
-
127
- context "and looking up for existent point" do
128
- subject { tree.find_first 12 }
129
-
130
- it { should be_a SegmentTree::Segment }
131
- its(:value) { should eq 'b' }
132
- end
133
126
 
134
- context "and looking up for non-existent point" do
135
- subject { tree.find_first 101 }
136
-
137
- it { should be_nil }
138
- end
139
- end
127
+ context "given hardly overlapping intervals" do
128
+ subject { SegmentTree.new(sample_overlapping2) }
140
129
 
141
- context "given partially overlapping intervals" do
142
- let(:tree) { SegmentTree.new(sample_overlapping) }
143
-
144
- context "and looking up for existent point" do
145
- subject { tree.find_first 11 }
146
-
147
- it { should be_a SegmentTree::Segment }
148
- its(:value) { should eq 'a' }
149
- end
150
- end
151
-
152
- context "given sparsed intervals" do
153
- let(:tree) { SegmentTree.new(sample_sparsed) }
154
-
155
- context "and looking up for existent point" do
156
- subject { tree.find_first 12 }
157
-
158
- it { should be_a SegmentTree::Segment }
159
- its(:value) { should eq 'b' }
160
- end
161
-
162
- context "and looking up for non-existent point" do
163
- subject { tree.find_first 8 }
164
-
165
- it { should be_nil }
166
- end
130
+ it { should query(12).and_return('b') }
131
+ it { should query(8).and_return(:nothing) }
167
132
  end
168
133
  end
169
134
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: segment_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-04 00:00:00.000000000Z
12
+ date: 2012-08-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &82956300 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '1.0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *82956300
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rspec
27
- requirement: &82956050 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,7 +37,12 @@ dependencies:
32
37
  version: '2.11'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *82956050
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '2.11'
36
46
  description: Tree data structure for storing segments. It allows querying which of
37
47
  the stored segments contain a given point.
38
48
  email: amikhailov83@gmail.com
@@ -66,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
76
  version: '0'
67
77
  requirements: []
68
78
  rubyforge_project:
69
- rubygems_version: 1.8.17
79
+ rubygems_version: 1.8.24
70
80
  signing_key:
71
81
  specification_version: 3
72
82
  summary: Tree data structure for storing segments. It allows querying which of the