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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 956de9d650db7ad301e9b4b903cd330b0630d866
4
- data.tar.gz: 32c2a3d2249e19ee5ac6ad6335d15952c9278816
3
+ metadata.gz: efbf94fb0f87fe1ae9932c5adff2d7d2e0eecceb
4
+ data.tar.gz: 6333edbdc572720acfd58530eb9dc8d6cce0641c
5
5
  SHA512:
6
- metadata.gz: 0b1fe690605128abd552bb893a6e4176343c453738f92efa9c935b8e7876d89ed358ca8ecf155f0af8ddd2d1e6b23ec712a5a778b0fd3c3700645eeab2cf8845
7
- data.tar.gz: 92c452c7bcd63b6651d545a3d41ea15b11e48d575b02899a54ebe0726d0668fee25d54a0ecb6c2e79fbbd6f06b277fd26818ee4eff896f521f573c2d048308b0
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
@@ -1,5 +1,5 @@
1
1
  # Encoding: utf-8
2
2
  # gem version
3
3
  class TimeFrame
4
- VERSION = '0.2.1'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/time_frame.rb CHANGED
@@ -10,3 +10,6 @@ require 'time_frame/time_frame_overlaps'
10
10
  require 'time_frame/time_frame_uniter'
11
11
 
12
12
  require 'time_frame/time_frame'
13
+
14
+ require 'time_frame/tree_node'
15
+ require 'time_frame/collection'
@@ -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
@@ -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.2.1
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-07-29 00:00:00.000000000 Z
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