time_frame 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/lib/time_frame/collection.rb +68 -0
- data/lib/time_frame/tree_node.rb +55 -0
- data/lib/time_frame/version.rb +1 -1
- data/lib/time_frame.rb +3 -0
- data/spec/collection_spec.rb +182 -0
- data/spec/time_frame_spec.rb +11 -0
- data/time_frame.gemspec +0 -1
- metadata +6 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efbf94fb0f87fe1ae9932c5adff2d7d2e0eecceb
|
4
|
+
data.tar.gz: 6333edbdc572720acfd58530eb9dc8d6cce0641c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7d02280bee7697e3403816914747f1881e0eabc328bccfbac33e1fe4789eaf43c058c51772589d2d9e888a956eb9fc8ebcf737f39582cd790dfb050424c2138
|
7
|
+
data.tar.gz: b361470e27cb987f9e653325a29db4e11489632a425e52dfaa140a29dac74a9b31a118811f4e0d9315d3e344cd2f9477336e3da3b30c820d2883c2df2b27d00a
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
# This collection supports the concept of interval trees to improve the
|
4
|
+
# access speed to intervals (or objects containing intervals) intersecting
|
5
|
+
# given time_frames or covering time elements
|
6
|
+
class Collection
|
7
|
+
attr_reader :tree_nodes, :root
|
8
|
+
def initialize(item_list = [], sorted = false, &block)
|
9
|
+
@block = block ? block : ->(item) { item }
|
10
|
+
@tree_nodes = item_list.map do |item|
|
11
|
+
TreeNode.new(item: item, &@block)
|
12
|
+
end
|
13
|
+
|
14
|
+
sort_list(@tree_nodes) unless sorted
|
15
|
+
build_tree(0, @tree_nodes.size - 1)
|
16
|
+
@root = @tree_nodes[(@tree_nodes.size - 1) / 2]
|
17
|
+
end
|
18
|
+
|
19
|
+
def all_covering(time)
|
20
|
+
result = []
|
21
|
+
add_covering(time, @root, result)
|
22
|
+
result.sort_by { |item | [@block.call(item).min, @block.call(item).max] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def all_intersecting(time_frame)
|
26
|
+
result = []
|
27
|
+
add_intersecting(time_frame, @root, result)
|
28
|
+
result.sort_by { |item | [@block.call(item).min, @block.call(item).max] }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def sort_list(item_list)
|
34
|
+
item_list.sort_by! do |item|
|
35
|
+
[item.time_frame.min, item.time_frame.max]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_tree(lower, upper, ancestor = nil, side = nil)
|
40
|
+
mid = (lower + upper) / 2
|
41
|
+
node = @tree_nodes[mid]
|
42
|
+
|
43
|
+
node.update_ancestor_relation(ancestor, side) if ancestor && side
|
44
|
+
|
45
|
+
build_tree(lower, mid - 1, node, :left) unless lower == mid
|
46
|
+
build_tree(mid + 1, upper, node, :right) unless upper == mid
|
47
|
+
|
48
|
+
node.update_child_range(node.min_child, node.max_child) if lower == upper
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_covering(time, node, result)
|
52
|
+
result << node.item if node.time_frame.cover?(time)
|
53
|
+
if node.continue_left_side_search_for_time?(time)
|
54
|
+
add_covering(time, node.left_child, result)
|
55
|
+
end
|
56
|
+
return unless node.continue_right_side_search_for_time?(time)
|
57
|
+
add_covering(time, node.right_child, result)
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_intersecting(time_frame, node, result)
|
61
|
+
result << node.item unless (node.time_frame & time_frame).empty?
|
62
|
+
if node.continue_left_side_search_for_time_frame?(time_frame)
|
63
|
+
add_intersecting(time_frame, node.left_child, result)
|
64
|
+
end
|
65
|
+
return unless node.continue_right_side_search_for_time_frame?(time_frame)
|
66
|
+
add_intersecting(time_frame, node.right_child, result)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
class Collection
|
3
|
+
# This is a helper class for the collection. It contains the node definition
|
4
|
+
# for the used tree structues.
|
5
|
+
class TreeNode
|
6
|
+
attr_accessor :max_child, :min_child, :left_child, :right_child
|
7
|
+
attr_reader :item, :time_frame, :ancestor
|
8
|
+
def initialize(args, &block)
|
9
|
+
@item = args.fetch(:item)
|
10
|
+
@time_frame = block.call(item)
|
11
|
+
# if ancestor is nil, then tree_item is root node
|
12
|
+
@ancestor = args.fetch(:ancestor, nil)
|
13
|
+
@left_child = args.fetch(:left_child, nil)
|
14
|
+
@right_child = args.fetch(:right_child, nil)
|
15
|
+
|
16
|
+
# if block is given use it to get item's time frame
|
17
|
+
@max_child = args.fetch(:max_child, @time_frame.max)
|
18
|
+
@min_child = args.fetch(:max_child, @time_frame.min)
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_ancestor_relation(new_ancestor, side)
|
22
|
+
@ancestor = new_ancestor
|
23
|
+
new_ancestor.left_child = self if side == :left
|
24
|
+
new_ancestor.right_child = self if side == :right
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_child_range(new_min_child, new_max_child)
|
28
|
+
@min_child = [@min_child, new_min_child].min
|
29
|
+
@max_child = [@max_child, new_max_child].max
|
30
|
+
ancestor.update_child_range(min_child, max_child) if ancestor
|
31
|
+
end
|
32
|
+
|
33
|
+
def continue_left_side_search_for_time?(time)
|
34
|
+
left_child &&
|
35
|
+
time >= left_child.min_child && time <= left_child.max_child
|
36
|
+
end
|
37
|
+
|
38
|
+
def continue_left_side_search_for_time_frame?(interval)
|
39
|
+
left_child &&
|
40
|
+
left_child.min_child <= interval.max &&
|
41
|
+
interval.min <= left_child.max_child
|
42
|
+
end
|
43
|
+
|
44
|
+
def continue_right_side_search_for_time?(time)
|
45
|
+
right_child &&
|
46
|
+
right_child.min_child <= time && time <= right_child.max_child
|
47
|
+
end
|
48
|
+
|
49
|
+
def continue_right_side_search_for_time_frame?(interval)
|
50
|
+
right_child &&
|
51
|
+
right_child.min_child <= interval.max &&
|
52
|
+
interval.min <= right_child.max_child
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/time_frame/version.rb
CHANGED
data/lib/time_frame.rb
CHANGED
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Collection do
|
4
|
+
|
5
|
+
let(:time_frame) { TimeFrame.new(min: Time.utc(2014), duration: 20.days) }
|
6
|
+
let(:time) { Time.utc(2014) }
|
7
|
+
|
8
|
+
describe '#all_covering' do
|
9
|
+
context 'when a pure time_frame tree is given' do
|
10
|
+
it 'returns all covering time_frames' do
|
11
|
+
time_frames = 20.times.map { |i| time_frame.shift_by((5 * i).days) }
|
12
|
+
tree = Collection.new(time_frames)
|
13
|
+
|
14
|
+
result = tree.all_covering(time)
|
15
|
+
expected_result = time_frames.select { |t| t.cover?(time) }
|
16
|
+
expect(result).to eq expected_result
|
17
|
+
|
18
|
+
result = tree.all_covering(time - 1.day)
|
19
|
+
expect(result).to eq []
|
20
|
+
|
21
|
+
result = tree.all_covering(time + 5.days)
|
22
|
+
expected_result = time_frames.select { |t| t.cover?(time + 5.days) }
|
23
|
+
expect(result).to eq expected_result
|
24
|
+
|
25
|
+
result = tree.all_covering(time + 7.days)
|
26
|
+
expected_result = time_frames.select { |t| t.cover?(time + 7.days) }
|
27
|
+
expect(result).to eq expected_result
|
28
|
+
|
29
|
+
result = tree.all_covering(time + 17.days)
|
30
|
+
expected_result = time_frames.select { |t| t.cover?(time + 17.days) }
|
31
|
+
expect(result).to eq expected_result
|
32
|
+
|
33
|
+
result = tree.all_covering(time + 42.days)
|
34
|
+
expected_result = time_frames.select { |t| t.cover?(time + 42.days) }
|
35
|
+
expect(result).to eq expected_result
|
36
|
+
|
37
|
+
result = tree.all_covering(time + 300.days)
|
38
|
+
expect(result).to eq []
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when objects containing time_frames are given' do
|
43
|
+
it 'returns all covering time_frames' do
|
44
|
+
objects = 20.times.map do |i|
|
45
|
+
OpenStruct.new(time_frame: time_frame.shift_by((5 * i).days))
|
46
|
+
end
|
47
|
+
tree = Collection.new(objects) { |item| item.time_frame }
|
48
|
+
|
49
|
+
result = tree.all_covering(time - 1.day)
|
50
|
+
expect(result).to eq []
|
51
|
+
|
52
|
+
result = tree.all_covering(time + 5.days)
|
53
|
+
expected_result = objects.select do |t|
|
54
|
+
t.time_frame.cover?(time + 5.days)
|
55
|
+
end
|
56
|
+
expect(result).to eq expected_result
|
57
|
+
|
58
|
+
result = tree.all_covering(time + 7.days)
|
59
|
+
expected_result = objects.select do |t|
|
60
|
+
t.time_frame.cover?(time + 7.days)
|
61
|
+
end
|
62
|
+
expect(result).to eq expected_result
|
63
|
+
|
64
|
+
result = tree.all_covering(time + 17.days)
|
65
|
+
expected_result = objects.select do |t|
|
66
|
+
t.time_frame.cover?(time + 17.days)
|
67
|
+
end
|
68
|
+
expect(result).to eq expected_result
|
69
|
+
|
70
|
+
result = tree.all_covering(time + 42.days)
|
71
|
+
expected_result = objects.select do |t|
|
72
|
+
t.time_frame.cover?(time + 42.days)
|
73
|
+
end
|
74
|
+
expect(result).to eq expected_result
|
75
|
+
|
76
|
+
result = tree.all_covering(time + 300.days)
|
77
|
+
expect(result).to eq []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#all_intersecting' do
|
83
|
+
context 'when a pure time_frame tree is given' do
|
84
|
+
it 'returns all intersecting time_frames' do
|
85
|
+
time_frames = 20.times.map { |i| time_frame.shift_by((5 * i).days) }
|
86
|
+
tree = Collection.new(time_frames)
|
87
|
+
interval = TimeFrame.new(min: time, duration: 1.hour)
|
88
|
+
|
89
|
+
result = tree.all_intersecting(interval.shift_by((-1).day))
|
90
|
+
expect(result).to eq []
|
91
|
+
|
92
|
+
result = tree.all_intersecting(interval)
|
93
|
+
expected_result = time_frames.select do |t|
|
94
|
+
!(t & (interval)).empty?
|
95
|
+
end
|
96
|
+
expect(result).to eq expected_result
|
97
|
+
|
98
|
+
this_interval = interval.shift_by(5.days)
|
99
|
+
result = tree.all_intersecting(this_interval)
|
100
|
+
expected_result = time_frames.select do |t|
|
101
|
+
!(t & (this_interval)).empty?
|
102
|
+
end
|
103
|
+
expect(result).to eq expected_result
|
104
|
+
|
105
|
+
this_interval = interval.shift_by(7.days)
|
106
|
+
result = tree.all_intersecting(this_interval)
|
107
|
+
expected_result = time_frames.select do |t|
|
108
|
+
!(t & (this_interval)).empty?
|
109
|
+
end
|
110
|
+
expect(result).to eq expected_result
|
111
|
+
|
112
|
+
this_interval = interval.shift_by(17.days)
|
113
|
+
result = tree.all_intersecting(this_interval)
|
114
|
+
expected_result = time_frames.select do |t|
|
115
|
+
!(t & (this_interval)).empty?
|
116
|
+
end
|
117
|
+
expect(result).to eq expected_result
|
118
|
+
|
119
|
+
this_interval = interval.shift_by(42.days)
|
120
|
+
result = tree.all_intersecting(this_interval)
|
121
|
+
expected_result = time_frames.select do |t|
|
122
|
+
!(t & (this_interval)).empty?
|
123
|
+
end
|
124
|
+
expect(result).to eq expected_result
|
125
|
+
|
126
|
+
result = tree.all_intersecting(interval.shift_by(300.days))
|
127
|
+
expect(result).to eq []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when objects containing time_frames are given' do
|
132
|
+
it 'returns all intersecting time_frames' do
|
133
|
+
objects = 20.times.map do |i|
|
134
|
+
OpenStruct.new(time_frame: time_frame.shift_by((5 * i).days))
|
135
|
+
end
|
136
|
+
tree = Collection.new(objects) { |item| item.time_frame }
|
137
|
+
interval = TimeFrame.new(min: time, duration: 1.hour)
|
138
|
+
|
139
|
+
result = tree.all_intersecting(interval.shift_by((-1).day))
|
140
|
+
expect(result).to eq []
|
141
|
+
|
142
|
+
result = tree.all_intersecting(interval)
|
143
|
+
expected_result = objects.select do |object|
|
144
|
+
!(object.time_frame & (interval)).empty?
|
145
|
+
end
|
146
|
+
expect(result).to eq expected_result
|
147
|
+
|
148
|
+
this_interval = interval.shift_by(5.days)
|
149
|
+
result = tree.all_intersecting(this_interval)
|
150
|
+
expected_result = objects.select do |object|
|
151
|
+
!(object.time_frame & (this_interval)).empty?
|
152
|
+
end
|
153
|
+
expect(result).to eq expected_result
|
154
|
+
|
155
|
+
this_interval = interval.shift_by(7.days)
|
156
|
+
result = tree.all_intersecting(this_interval)
|
157
|
+
expected_result = objects.select do |object|
|
158
|
+
!(object.time_frame & (this_interval)).empty?
|
159
|
+
end
|
160
|
+
expect(result).to eq expected_result
|
161
|
+
|
162
|
+
this_interval = interval.shift_by(17.days)
|
163
|
+
result = tree.all_intersecting(this_interval)
|
164
|
+
expected_result = objects.select do |object|
|
165
|
+
!(object.time_frame & (this_interval)).empty?
|
166
|
+
end
|
167
|
+
expect(result).to eq expected_result
|
168
|
+
|
169
|
+
this_interval = interval.shift_by(42.days)
|
170
|
+
result = tree.all_intersecting(this_interval)
|
171
|
+
expected_result = objects.select do |object|
|
172
|
+
!(object.time_frame & (this_interval)).empty?
|
173
|
+
end
|
174
|
+
expect(result).to eq expected_result
|
175
|
+
|
176
|
+
result = tree.all_intersecting(interval.shift_by(300.days))
|
177
|
+
expect(result).to eq []
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/spec/time_frame_spec.rb
CHANGED
@@ -77,6 +77,17 @@ describe TimeFrame do
|
|
77
77
|
subject { super().max }
|
78
78
|
it { should eq time }
|
79
79
|
end
|
80
|
+
|
81
|
+
context 'when min is a date' do
|
82
|
+
context 'and duration is 0' do
|
83
|
+
it 'should be valid' do
|
84
|
+
expect do
|
85
|
+
TimeFrame.new(min: Date.new(2012), duration: 0.seconds)
|
86
|
+
end.not_to raise_error
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
80
91
|
end
|
81
92
|
context 'and time sframe covers a DST shift' do
|
82
93
|
let(:time) do
|
data/time_frame.gemspec
CHANGED
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
|
29
29
|
spec.add_development_dependency 'rake', '~> 10.3.2'
|
30
30
|
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
31
|
-
spec.add_development_dependency 'bundler', '~> 1.6.1'
|
32
31
|
spec.add_development_dependency 'simplecov', '~> 0.8.2'
|
33
32
|
spec.add_development_dependency 'rubocop', '~> 0.23.0'
|
34
33
|
spec.add_dependency 'activesupport', '~> 4.1.1'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_frame
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Derichs
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-10-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
@@ -40,20 +40,6 @@ dependencies:
|
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
version: 3.0.0
|
43
|
-
- !ruby/object:Gem::Dependency
|
44
|
-
name: bundler
|
45
|
-
requirement: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - "~>"
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: 1.6.1
|
50
|
-
type: :development
|
51
|
-
prerelease: false
|
52
|
-
version_requirements: !ruby/object:Gem::Requirement
|
53
|
-
requirements:
|
54
|
-
- - "~>"
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: 1.6.1
|
57
43
|
- !ruby/object:Gem::Dependency
|
58
44
|
name: simplecov
|
59
45
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,13 +99,16 @@ files:
|
|
113
99
|
- README.md
|
114
100
|
- Rakefile
|
115
101
|
- lib/time_frame.rb
|
102
|
+
- lib/time_frame/collection.rb
|
116
103
|
- lib/time_frame/empty.rb
|
117
104
|
- lib/time_frame/time_frame.rb
|
118
105
|
- lib/time_frame/time_frame_covered.rb
|
119
106
|
- lib/time_frame/time_frame_overlaps.rb
|
120
107
|
- lib/time_frame/time_frame_splitter.rb
|
121
108
|
- lib/time_frame/time_frame_uniter.rb
|
109
|
+
- lib/time_frame/tree_node.rb
|
122
110
|
- lib/time_frame/version.rb
|
111
|
+
- spec/collection_spec.rb
|
123
112
|
- spec/spec_helper.rb
|
124
113
|
- spec/time_frame_spec.rb
|
125
114
|
- time_frame.gemspec
|
@@ -148,5 +137,6 @@ signing_key:
|
|
148
137
|
specification_version: 4
|
149
138
|
summary: Ruby gem that offers support for time frames
|
150
139
|
test_files:
|
140
|
+
- spec/collection_spec.rb
|
151
141
|
- spec/spec_helper.rb
|
152
142
|
- spec/time_frame_spec.rb
|