segment_tree 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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