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.
- data/lib/segment_tree/version.rb +1 -1
- data/lib/segment_tree.rb +53 -79
- data/spec/segment_tree_spec.rb +68 -103
- metadata +17 -7
data/lib/segment_tree/version.rb
CHANGED
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.
|
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
|
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
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
112
|
-
@root = nodes.first
|
56
|
+
@segments.sort! unless sorted
|
113
57
|
end
|
114
58
|
|
115
|
-
# Find
|
116
|
-
# @return [
|
59
|
+
# Find first interval containing point +x+.
|
60
|
+
# @return [Segment|NilClass]
|
117
61
|
def find(x)
|
118
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
data/spec/segment_tree_spec.rb
CHANGED
@@ -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..
|
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
|
-
{
|
52
|
+
{7..9 => "a",
|
17
53
|
4..6 => "b",
|
18
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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 "
|
106
|
+
describe "querying" do
|
67
107
|
context "given spanned intervals" do
|
68
|
-
|
108
|
+
subject { SegmentTree.new(sample_spanned) }
|
69
109
|
|
70
|
-
|
71
|
-
|
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
|
-
|
89
|
-
|
90
|
-
context "and looking up for existent point" do
|
91
|
-
subject { tree.find 11 }
|
115
|
+
subject { SegmentTree.new(sample_overlapping) }
|
92
116
|
|
93
|
-
|
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
|
-
|
121
|
+
subject { SegmentTree.new(sample_sparsed) }
|
104
122
|
|
105
|
-
|
106
|
-
|
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
|
-
|
135
|
-
|
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
|
-
|
142
|
-
|
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
|
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-
|
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:
|
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:
|
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:
|
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:
|
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.
|
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
|