interval_notation 0.1.0 → 0.1.1
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/benchmark/benchmark.rb +51 -0
- data/lib/interval_notation/basic_intervals.rb +10 -0
- data/lib/interval_notation/interval_set.rb +80 -4
- data/lib/interval_notation/version.rb +1 -1
- data/spec/basic_intervals_spec.rb +1 -1
- data/spec/intersection_spec.rb +275 -0
- data/spec/interval_notation_spec.rb +9 -157
- data/spec/spec_helper.rb +35 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4160164096cbfcc4f171ce43a2c460fb1d3df70c
|
4
|
+
data.tar.gz: 3b4f604be240d5d714bd1eacfc3b158c41de7287
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5988e37f501ec499213a5eee17c63012c99d5d55df1f3e076e62bab39f11b03f3a5b60958e109b8b07237499bab442bb0688f3da0a176da827d3f487fa62fb8
|
7
|
+
data.tar.gz: c89451349d28a973a45ec6ed7017be6502be18bb9e72745031d638b2763aaa825d57266abc708696e8c5fbb7bc5ab0eee44bd2ee9a1038beb9082ef994d5fe4d
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require_relative '../lib/interval_notation'
|
3
|
+
include IntervalNotation::Syntax::Long
|
4
|
+
|
5
|
+
num_intervals_to_unite = 10_000
|
6
|
+
srand(13)
|
7
|
+
random_intervals_1 = num_intervals_to_unite.times.map{|i| 2.times.map{ 0.3*i + rand }.sort }.reject{|a,b| a==b }.map{|a,b| closed_closed(a,b) }
|
8
|
+
random_intervals_2 = num_intervals_to_unite.times.map{|i| 2.times.map{ 3000 + 0.3*i + rand }.sort }.reject{|a,b| a==b }.map{|a,b| closed_closed(a,b) }
|
9
|
+
|
10
|
+
N = 10
|
11
|
+
M = 100_000
|
12
|
+
|
13
|
+
dispersed_interval_1 = IntervalNotation::Operations.union(random_intervals_1)
|
14
|
+
dispersed_interval_2 = IntervalNotation::Operations.union(random_intervals_2)
|
15
|
+
|
16
|
+
singular_interval_1 = closed_closed(500 + rand, 1000 + rand)
|
17
|
+
singular_interval_2 = closed_closed(700 + rand, 1700 + rand)
|
18
|
+
|
19
|
+
Benchmark.bm do |benchmark_report|
|
20
|
+
|
21
|
+
benchmark_report.report("old intersection check for dispersed intervals to singular interval (#{N} times)") do
|
22
|
+
N.times do
|
23
|
+
dispersed_interval_1.intersection(singular_interval_1).empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
benchmark_report.report("intersect? dispersed intervals to singular interval (#{M} times)") do
|
28
|
+
M.times do
|
29
|
+
dispersed_interval_1.intersect?(singular_interval_1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
benchmark_report.report("intersect? singular interval to dispersed intervals (#{M} times)") do
|
34
|
+
M.times do
|
35
|
+
singular_interval_1.intersect?(dispersed_interval_1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
benchmark_report.report("intersect? two dispersed intervals (#{M} times)") do
|
40
|
+
M.times do
|
41
|
+
dispersed_interval_2.intersect?(dispersed_interval_1)
|
42
|
+
dispersed_interval_1.intersect?(dispersed_interval_2)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
benchmark_report.report("Unite #{num_intervals_to_unite} intervals (#{N} times)") do
|
47
|
+
N.times do
|
48
|
+
IntervalNotation::Operations.union(random_intervals_1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -47,6 +47,11 @@ module IntervalNotation
|
|
47
47
|
def singular_point?; false; end
|
48
48
|
def hash; [@from, @to, include_from?, include_to?].hash; end;
|
49
49
|
def eql?(other); other.class.equal?(self.class) && from.eql?(other.from) && to.eql?(other.to); end
|
50
|
+
|
51
|
+
# include position and its vicinity
|
52
|
+
def deep_include_position?(pos)
|
53
|
+
from < pos && pos < to
|
54
|
+
end
|
50
55
|
end
|
51
56
|
|
52
57
|
class OpenOpenInterval
|
@@ -190,6 +195,11 @@ module IntervalNotation
|
|
190
195
|
def interval_boundaries(interval_index)
|
191
196
|
BoundaryPoint.new(from, true, nil, interval_index, false)
|
192
197
|
end
|
198
|
+
|
199
|
+
# include position and its vicinity (point can't include vicinity of a position)
|
200
|
+
def deep_include_position?(pos)
|
201
|
+
false
|
202
|
+
end
|
193
203
|
end
|
194
204
|
|
195
205
|
def self.interval_by_boundary_inclusion(include_from, from, include_to, to)
|
@@ -53,10 +53,15 @@ module IntervalNotation
|
|
53
53
|
# Checks whether an interval set contains certain position.
|
54
54
|
# Operation complexity is O(ln N), where N is a number of contiguous regions in an interval set
|
55
55
|
def include_position?(value)
|
56
|
-
interval = @intervals.bsearch{|interv| interv.to
|
56
|
+
interval = @intervals.bsearch{|interv| value <= interv.to }
|
57
57
|
interval && interval.include_position?(value)
|
58
58
|
end
|
59
59
|
|
60
|
+
def deep_include_position?(value)
|
61
|
+
interval = @intervals.bsearch{|interv| value <= interv.to }
|
62
|
+
interval && interval.deep_include_position?(value)
|
63
|
+
end
|
64
|
+
|
60
65
|
# Checks whether an interval set contains another interval set. Alias: +#include?+
|
61
66
|
def contain?(other)
|
62
67
|
self.intersection(other) == other
|
@@ -69,10 +74,81 @@ module IntervalNotation
|
|
69
74
|
end
|
70
75
|
alias covered_by? contained_by?
|
71
76
|
|
72
|
-
|
73
|
-
|
77
|
+
|
78
|
+
def bsearch_last_not_meeting_condition(arr)
|
79
|
+
found_ind = (0...arr.size).bsearch{|idx| yield(arr[idx]) } # find first not meeting condition
|
80
|
+
if found_ind
|
81
|
+
found_ind == 0 ? nil : arr[found_ind - 1]
|
82
|
+
else
|
83
|
+
arr.last
|
84
|
+
end
|
85
|
+
end
|
86
|
+
private :bsearch_last_not_meeting_condition
|
87
|
+
|
88
|
+
# Checks intersection with a single (basic) interval in a O(log N) time.
|
89
|
+
def intersect_single_interval?(interval) # :nodoc:
|
90
|
+
from = interval.from
|
91
|
+
to = interval.to
|
92
|
+
|
93
|
+
# If from is against a singular point, then ignore it.
|
94
|
+
#..............................pos............................
|
95
|
+
# interval_left_to_pos...................interval_right_to_pos
|
96
|
+
#
|
97
|
+
#..............................pos.............................
|
98
|
+
# interval_left_to_pos........point.......interval_right_to_pos
|
99
|
+
# reversed_intervals = @intervals.reverse
|
100
|
+
|
101
|
+
# find last interval +interv+ such that (interv.from < from)
|
102
|
+
left_to_start = bsearch_last_not_meeting_condition(@intervals){|interv| interv.from >= from }
|
103
|
+
# find last interval +interv+ such that (interv.from < to)
|
104
|
+
left_to_finish = bsearch_last_not_meeting_condition(@intervals){|interv| interv.from >= to }
|
105
|
+
|
106
|
+
# find first interval +interv+ such that from < interv.to
|
107
|
+
right_to_start = @intervals.bsearch{|interv| from < interv.to }
|
108
|
+
# find first interval +interv+ such that to < interv.to
|
109
|
+
right_to_finish = @intervals.bsearch{|interv| to < interv.to }
|
110
|
+
|
111
|
+
# If +from+ or +to+ is included in an interval, it is either
|
112
|
+
# a) deeply immersed (i.e. lie within interval with its vicinity) in it or
|
113
|
+
# b) just adjoins an interval(then it matters, whether it's included)
|
114
|
+
# If neither of points lie on an interval set, then it can still intersect an interval
|
115
|
+
# if +from+ and +to+ points are between different pairs of intervals.
|
116
|
+
# Problems come with singular points. If one is against interval's from or to,
|
117
|
+
# it is either treated as included, or is a deleted point going as either left or right boundary.
|
118
|
+
# It's hard to distinguish which boundary it is, so we just ignore a point if it is against +from+ or +to+
|
119
|
+
# (see a trick above) and treat intervals going the same side from a point as non-intersecting it.
|
120
|
+
# If no singular point exist against +from+ and +to+ positions, the non-overlapping interval have both
|
121
|
+
# +left_to_start == left_to_finish+ and +right_to_start == right_to_finish+.
|
122
|
+
# Otherwise +interval+ overlap (cover) some interval.
|
123
|
+
include_position?(from) && (deep_include_position?(from) || interval.include_from?) ||
|
124
|
+
include_position?(to) && (deep_include_position?(to) || interval.include_to?) ||
|
125
|
+
!(left_to_start == left_to_finish || right_to_start == right_to_finish)
|
126
|
+
end
|
127
|
+
protected :intersect_single_interval?
|
128
|
+
|
129
|
+
# Checks whether intervals intersect in O(M*log N) where M and N are interval set sizes
|
130
|
+
def intersect_n_log_n?(other)
|
131
|
+
# sz_1 = num_connected_components + 2
|
132
|
+
# sz_2 = other.num_connected_components + 2
|
133
|
+
# if sz_1*Math.log2(sz_2) < sz_2 * Math.log2(sz_1)
|
134
|
+
|
135
|
+
# each of N intervals intersection check takes log(M). We prefer to take small N, large M than vice-versa
|
136
|
+
if @intervals.size < other.intervals.size
|
137
|
+
@intervals.any? do |segment|
|
138
|
+
other.intersect_single_interval?(segment)
|
139
|
+
end
|
140
|
+
else
|
141
|
+
other.intervals.any? do |segment|
|
142
|
+
intersect_single_interval?(segment)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
protected :intersect_n_log_n?
|
147
|
+
|
148
|
+
# Checks whether an interval set intersects another interval set. Alias: +#overlap?+
|
74
149
|
def intersect?(other)
|
75
|
-
|
150
|
+
intersect_n_log_n?(other) # ToDo: balance different implementations for different interval set sizes
|
151
|
+
# ! intersection(other).empty? # Simplest and too slow implementation
|
76
152
|
end
|
77
153
|
alias overlap? intersect?
|
78
154
|
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe IntervalNotation do
|
4
|
+
describe IntervalSet do
|
5
|
+
|
6
|
+
describe '#intersection' do
|
7
|
+
{
|
8
|
+
[oo(1,3), oo(1,3)] => oo(1,3),
|
9
|
+
[oo(1,3), oc(1,3)] => oo(1,3),
|
10
|
+
[oo(1,3), co(1,3)] => oo(1,3),
|
11
|
+
[oo(1,3), cc(1,3)] => oo(1,3),
|
12
|
+
|
13
|
+
[pt(2), pt(3)] => Empty,
|
14
|
+
[pt(3), pt(3)] => pt(3),
|
15
|
+
[pt(3)|pt(5), pt(3)|pt(5)] => pt(3)|pt(5),
|
16
|
+
[pt(3)|pt(5), pt(3)|pt(7)] => pt(3),
|
17
|
+
|
18
|
+
[oo(1,3)|oo(5,7), oo(1,3)|oo(5,7)] => oo(1,3)|oo(5,7),
|
19
|
+
[oo(1,3)|co(5,7), oo(1,3)|oo(5,7)] => oo(1,3)|oo(5,7),
|
20
|
+
[oo(1,3)|oo(5,7), oo(1,3)|co(5,7)] => oo(1,3)|oo(5,7),
|
21
|
+
[oo(1,3)|co(5,7), oo(1,3)|co(5,7)] => oo(1,3)|co(5,7),
|
22
|
+
|
23
|
+
[oo(1,3)|oo(3,5), oo(1,3)] => oo(1,3),
|
24
|
+
[oo(1,3)|oo(3,5), oo(1,3)|oo(7,9)] => oo(1,3),
|
25
|
+
[oo(1,3)|oo(3,5), oo(3,5)] => oo(3,5),
|
26
|
+
[oo(1,3)|oo(3,5), oo(1,3)|oo(3,5)] => oo(1,3)|oo(3,5),
|
27
|
+
|
28
|
+
[oo(1,3), oo(4,5)] => Empty,
|
29
|
+
[oo(1,3), oo(3,5)] => Empty,
|
30
|
+
[oo(1,3), co(3,5)] => Empty,
|
31
|
+
[oc(1,3), oo(3,5)] => Empty,
|
32
|
+
[oc(1,3), co(3,5)] => pt(3),
|
33
|
+
|
34
|
+
[oo(1,3), oo(0,5)] => oo(1,3),
|
35
|
+
[oc(1,3), oo(0,5)] => oc(1,3),
|
36
|
+
[co(1,3), oo(0,5)] => co(1,3),
|
37
|
+
[cc(1,3), oo(0,5)] => cc(1,3),
|
38
|
+
|
39
|
+
[oo(1,3), oo(1,5)] => oo(1,3),
|
40
|
+
[co(1,3), oo(1,5)] => oo(1,3),
|
41
|
+
[oc(1,3), oo(1,5)] => oc(1,3),
|
42
|
+
[cc(1,3), oo(1,5)] => oc(1,3),
|
43
|
+
[co(3,5), oo(1,5)] => co(3,5),
|
44
|
+
|
45
|
+
[oo(1,3), oo(2,5)] => oo(2,3),
|
46
|
+
[oc(1,3), oo(2,5)] => oc(2,3),
|
47
|
+
[oo(1,3), co(2,5)] => co(2,3),
|
48
|
+
[oc(1,3), co(2,5)] => cc(2,3),
|
49
|
+
|
50
|
+
[oo(1,3), pt(2)] => pt(2),
|
51
|
+
[oo(1,3), pt(3)] => Empty,
|
52
|
+
[oc(1,3), pt(3)] => pt(3),
|
53
|
+
[oo(1,3), pt(4)] => Empty,
|
54
|
+
[oc(1,3), pt(4)] => Empty,
|
55
|
+
|
56
|
+
[oo(1,3)|oo(3,5), oo(2,3)|oo(3,7)] => oo(2,3)|oo(3,5),
|
57
|
+
[oo(1,3)|oo(3,5), oo(2,7)] => oo(2,3)|oo(3,5),
|
58
|
+
|
59
|
+
[oo(2,6), oo(1,3)|co(5,7)] => oo(2,3)|co(5,6),
|
60
|
+
[oo(1,6), oo(1,3)|co(5,7)] => oo(1,3)|co(5,6),
|
61
|
+
[oo(0,6), oo(1,3)|co(5,7)] => oo(1,3)|co(5,6),
|
62
|
+
[oo(0,6), oo(1,3)|co(5,6)] => oo(1,3)|co(5,6),
|
63
|
+
|
64
|
+
|
65
|
+
}.each do |intervals, answer|
|
66
|
+
it "IntervalNotation::Operations.intersection(#{intervals.map(&:to_s).join(',')} should equal #{answer}" do
|
67
|
+
expect( IntervalNotation::Operations.intersection(intervals) ).to eq answer
|
68
|
+
end
|
69
|
+
|
70
|
+
it "IntervalNotation::Operations.intersection(#{intervals.map(&:to_s).join(',')} should equal consequent unite: #{intervals.map(&:to_s).join('&')}" do
|
71
|
+
expect( IntervalNotation::Operations.intersection(intervals) ).to eq intervals.inject(&:intersection)
|
72
|
+
end
|
73
|
+
|
74
|
+
if intervals.size == 2
|
75
|
+
interval_1, interval_2 = intervals
|
76
|
+
it "#{interval_1} & #{interval_2} should equal #{answer}" do
|
77
|
+
expect( interval_1.intersection(interval_2) ).to eq answer
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
{
|
83
|
+
[oo(1,3), oo(1,3), oo(1,3)] => oo(1,3),
|
84
|
+
[oo(1,3), cc(1,3), oo(1,3)] => oo(1,3),
|
85
|
+
[oo(1,3), cc(0,5), cc(1,3)] => oo(1,3),
|
86
|
+
[cc(1,5), cc(3,7), cc(1,3)|cc(5,7)] => pt(3)|pt(5),
|
87
|
+
[oo(1,5), oo(3,7), oo(1,3)|oo(5,7)] => Empty,
|
88
|
+
[oo(1,5), oc(1,3), co(3,5)] => pt(3)
|
89
|
+
}.each do |intervals, answer|
|
90
|
+
it "#{intervals.map(&:to_s).join('&')} should equal #{answer}" do
|
91
|
+
expect( IntervalNotation::Operations.intersection(intervals) ).to eq answer
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Very extensive automatic testing
|
97
|
+
describe '#intersect?', too_extensive_testing: true, slow: true do
|
98
|
+
examples = []
|
99
|
+
examples += [
|
100
|
+
[[oo(1,3)|oo(4,6)|oo(8,10), oo(3,8)], true],
|
101
|
+
[[oo(1,3)|oo(4,6)|oo(8,10)|cc(12,14) , oo(3,8)], true],
|
102
|
+
[[oo(1,3)|cc(4,6)|oo(8,10), oo(3,8)], true],
|
103
|
+
[[oo(1,3)|pt(5)|oo(8,10), oo(3,8)], true],
|
104
|
+
[[oo(1,3)|oo(8,10), oo(3,8)], false],
|
105
|
+
[[cc(1,3)|cc(8,10), oo(3,8)], false],
|
106
|
+
|
107
|
+
[[oo(1,3)|oo(8,10), cc(3,8)], false],
|
108
|
+
[[co(1,3)|oc(8,10), cc(3,8)], false],
|
109
|
+
[[oc(1,3)|oo(8,10), cc(3,8)], true],
|
110
|
+
[[oo(1,3)|co(8,10), cc(3,8)], true],
|
111
|
+
[[oc(1,3)|co(8,10), cc(3,8)], true],
|
112
|
+
[[cc(1,3)|cc(8,10), cc(3,8)], true],
|
113
|
+
|
114
|
+
[[oc(1,3)|oo(8,10), co(3,8)], true],
|
115
|
+
[[oc(1,3)|cc(8,10), co(3,8)], true],
|
116
|
+
[[oo(1,3)|oo(8,10), co(3,8)], false],
|
117
|
+
[[oo(1,3)|cc(8,10), co(3,8)], false],
|
118
|
+
|
119
|
+
[[oo(1,3)|oo(8,10), oc(3,8)], false],
|
120
|
+
[[oo(1,3)|oc(8,10), oc(3,8)], false],
|
121
|
+
[[oo(1,3)|co(8,10), oc(3,8)], true],
|
122
|
+
[[oo(1,3)|cc(8,10), oc(3,8)], true],
|
123
|
+
|
124
|
+
[[oo(1,3)|oo(8,10)|cc(12,14), oo(3,8)], false],
|
125
|
+
[[oo(1,3)|oo(8,10)|cc(12,14), cc(3,8)], false],
|
126
|
+
[[oc(1,3)|oo(8,10)|cc(12,14), cc(3,8)], true],
|
127
|
+
[[oc(1,3)|oo(8,10)|cc(12,14), co(3,8)], true],
|
128
|
+
[[oo(1,3)|co(8,10)|cc(12,14), cc(3,8)], true],
|
129
|
+
[[oo(1,3)|co(8,10)|cc(12,14), oc(3,8)], true],
|
130
|
+
]
|
131
|
+
examples += interval_for_each_boundary_type(1,3).flat_map{|interval|
|
132
|
+
[
|
133
|
+
[[interval, pt(2)], true],
|
134
|
+
[[interval, pt(4)], false],
|
135
|
+
]
|
136
|
+
}
|
137
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 1,3, true)
|
138
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 1,2, true)
|
139
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 2,3, true)
|
140
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 1.5,2.5, true)
|
141
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 2,4, true)
|
142
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 0,2, true)
|
143
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 0,4, true)
|
144
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 0,4, true)
|
145
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, 4,5, false)
|
146
|
+
examples += pairs_of_intervals_for_each_boundary_with_answer(1,3, -1,0, false)
|
147
|
+
|
148
|
+
examples += [
|
149
|
+
[[oo(1,3), oo(3,4)], false],
|
150
|
+
[[oo(1,3), oc(3,4)], false],
|
151
|
+
[[oo(1,3), co(3,4)], false],
|
152
|
+
[[oo(1,3), cc(3,4)], false],
|
153
|
+
[[oo(1,3), oo(0,1)], false],
|
154
|
+
[[oo(1,3), oc(0,1)], false],
|
155
|
+
[[oo(1,3), co(0,1)], false],
|
156
|
+
[[oo(1,3), cc(0,1)], false],
|
157
|
+
[[oo(1,3), pt(3)], false],
|
158
|
+
[[oo(1,3), pt(1)], false],
|
159
|
+
|
160
|
+
[[co(1,3), oo(3,4)], false],
|
161
|
+
[[co(1,3), oc(3,4)], false],
|
162
|
+
[[co(1,3), co(3,4)], false],
|
163
|
+
[[co(1,3), cc(3,4)], false],
|
164
|
+
[[co(1,3), oo(0,1)], false],
|
165
|
+
[[co(1,3), oc(0,1)], true],
|
166
|
+
[[co(1,3), co(0,1)], false],
|
167
|
+
[[co(1,3), cc(0,1)], true],
|
168
|
+
[[co(1,3), pt(1)], true],
|
169
|
+
[[co(1,3), pt(3)], false],
|
170
|
+
|
171
|
+
[[oc(1,3), oo(3,4)], false],
|
172
|
+
[[oc(1,3), oc(3,4)], false],
|
173
|
+
[[oc(1,3), co(3,4)], true],
|
174
|
+
[[oc(1,3), cc(3,4)], true],
|
175
|
+
[[oc(1,3), oo(0,1)], false],
|
176
|
+
[[oc(1,3), oc(0,1)], false],
|
177
|
+
[[oc(1,3), co(0,1)], false],
|
178
|
+
[[oc(1,3), cc(0,1)], false],
|
179
|
+
[[oc(1,3), pt(1)], false],
|
180
|
+
[[oc(1,3), pt(3)], true],
|
181
|
+
|
182
|
+
[[cc(1,3), oo(3,4)], false],
|
183
|
+
[[cc(1,3), oc(3,4)], false],
|
184
|
+
[[cc(1,3), co(3,4)], true],
|
185
|
+
[[cc(1,3), cc(3,4)], true],
|
186
|
+
[[cc(1,3), oo(0,1)], false],
|
187
|
+
[[cc(1,3), oc(0,1)], true],
|
188
|
+
[[cc(1,3), co(0,1)], false],
|
189
|
+
[[cc(1,3), cc(0,1)], true],
|
190
|
+
[[cc(1,3), pt(1)], true],
|
191
|
+
[[cc(1,3), pt(3)], true],
|
192
|
+
|
193
|
+
|
194
|
+
[[oo(1,3), oo(4,5)], false],
|
195
|
+
[[cc(1,3), cc(4,5)], false],
|
196
|
+
[[cc(1,3), pt(2)|cc(4,5)], true],
|
197
|
+
[[co(1,3), pt(2)|cc(4,5)], true],
|
198
|
+
[[cc(1,3), pt(3)|cc(4,5)], true],
|
199
|
+
[[co(1,3), pt(3)|cc(4,5)], false],
|
200
|
+
[[cc(1,3), cc(4,5)|pt(6)], false],
|
201
|
+
]
|
202
|
+
|
203
|
+
# Enable to add extensive tests for #intersect? testing
|
204
|
+
examples = examples.flat_map{|(interval_1,interval_2), ans|
|
205
|
+
[
|
206
|
+
[[interval_1, interval_2], ans],
|
207
|
+
[[interval_1 | pt(100), interval_2], ans],
|
208
|
+
[[interval_1, interval_2 | pt(-100)], ans],
|
209
|
+
[[interval_1 | pt(100), interval_2 | pt(-100)], ans],
|
210
|
+
] +
|
211
|
+
interval_for_each_boundary_type(-150, -100).flat_map {|interval_3| # distant intervals doesn't interfere intersection property
|
212
|
+
[
|
213
|
+
[[interval_1, interval_2 | interval_3], ans],
|
214
|
+
[[interval_1 | interval_3, interval_2], ans]
|
215
|
+
]
|
216
|
+
} +
|
217
|
+
interval_for_each_boundary_type(100, 150).flat_map {|interval_3|
|
218
|
+
[
|
219
|
+
[[interval_1, interval_2 | interval_3], ans],
|
220
|
+
[[interval_1 | interval_3, interval_2], ans]
|
221
|
+
]
|
222
|
+
} +
|
223
|
+
interval_for_each_boundary_type(-150, -100).flat_map {|interval_3|
|
224
|
+
interval_for_each_boundary_type(100, 150).flat_map {|interval_4|
|
225
|
+
[
|
226
|
+
[[interval_1, interval_2|interval_3|interval_4], ans],
|
227
|
+
[[interval_1|interval_3|interval_4, interval_2], ans],
|
228
|
+
[[interval_1|interval_3, interval_2|interval_4], ans],
|
229
|
+
[[interval_1|interval_4, interval_2|interval_3], ans],
|
230
|
+
]
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
examples += [
|
236
|
+
[[lt(10), oo(1,3)], true],
|
237
|
+
[[lt(10), oo(11,12)], false],
|
238
|
+
[[lt(10), oo(10,11)], false],
|
239
|
+
[[le(10), co(10,11)], true],
|
240
|
+
[[le(10), le(9)], true],
|
241
|
+
[[le(10), le(11)], true],
|
242
|
+
[[le(10), ge(11)], false],
|
243
|
+
[[le(10), ge(10)], true],
|
244
|
+
[[le(10), gt(10)], false],
|
245
|
+
[[le(10), gt(11)], false],
|
246
|
+
]
|
247
|
+
examples += examples.flat_map{|(interval_1, interval_2), ans|
|
248
|
+
[interval_1, interval_2]
|
249
|
+
}.uniq.flat_map{|interval|
|
250
|
+
[
|
251
|
+
[[interval, Empty], false],
|
252
|
+
[[interval, R], interval != Empty],
|
253
|
+
]
|
254
|
+
}
|
255
|
+
|
256
|
+
examples.each do |(interval_1, interval_2), answer|
|
257
|
+
if answer
|
258
|
+
it "#{interval_1}.intersect?(#{interval_2}) should be truthy" do
|
259
|
+
expect( interval_1.intersect?(interval_2) ).to be_truthy
|
260
|
+
end
|
261
|
+
it "#{interval_2}.intersect?(#{interval_1}) should be truthy" do
|
262
|
+
expect( interval_2.intersect?(interval_1) ).to be_truthy
|
263
|
+
end
|
264
|
+
else
|
265
|
+
it "#{interval_1}.intersect?(#{interval_2}) should be falsy" do
|
266
|
+
expect( interval_1.intersect?(interval_2) ).to be_falsy
|
267
|
+
end
|
268
|
+
it "#{interval_2}.intersect?(#{interval_1}) should be falsy" do
|
269
|
+
expect( interval_2.intersect?(interval_1) ).to be_falsy
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -1,21 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
include IntervalNotation
|
4
|
-
include IntervalNotation::BasicIntervals
|
5
|
-
include IntervalNotation::Syntax::Short
|
6
|
-
|
7
|
-
def each_combination_of_intervals(intervals)
|
8
|
-
basic_intervals = intervals.flat_map(&:intervals)
|
9
|
-
(1..basic_intervals.size / 2).each do |chunk_1_size|
|
10
|
-
indices = basic_intervals.size.times.to_a
|
11
|
-
indices.combination(chunk_1_size).each do |chunk_1_indices|
|
12
|
-
chunk_2_indices = indices - chunk_1_indices
|
13
|
-
chunk_1 = IntervalNotation::Operations.union(chunk_1_indices.map{|i| IntervalSet.new([basic_intervals[i]]) })
|
14
|
-
chunk_2 = IntervalNotation::Operations.union(chunk_2_indices.map{|i| IntervalSet.new([basic_intervals[i]]) })
|
15
|
-
yield chunk_1, chunk_2
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
1
|
+
require_relative 'spec_helper'
|
19
2
|
|
20
3
|
describe IntervalNotation do
|
21
4
|
describe IntervalSet do
|
@@ -53,14 +36,14 @@ describe IntervalNotation do
|
|
53
36
|
[OpenOpenInterval.new(-Float::INFINITY, 1), Point.new(0)],
|
54
37
|
[OpenOpenInterval.new(-Float::INFINITY, 1), Point.new(1)],
|
55
38
|
[Point.new(2), OpenOpenInterval.new(-Float::INFINITY, 1)],
|
56
|
-
|
39
|
+
|
57
40
|
[Point.new(11), OpenOpenInterval.new(10, Float::INFINITY)],
|
58
41
|
[Point.new(10), OpenOpenInterval.new(10, Float::INFINITY)],
|
59
42
|
[OpenOpenInterval.new(10, Float::INFINITY), Point.new(11)],
|
60
43
|
[OpenOpenInterval.new(10, Float::INFINITY), Point.new(9)],
|
61
44
|
[OpenOpenInterval.new(10, Float::INFINITY), OpenOpenInterval.new(9,13)],
|
62
45
|
[OpenOpenInterval.new(10, Float::INFINITY), OpenOpenInterval.new(11,13)],
|
63
|
-
|
46
|
+
|
64
47
|
[OpenOpenInterval.new(1,3), Point.new(3)],
|
65
48
|
[OpenOpenInterval.new(1,3), ClosedOpenInterval.new(3,4)],
|
66
49
|
[OpenClosedInterval.new(1,3), ClosedOpenInterval.new(3,4)],
|
@@ -201,7 +184,7 @@ describe IntervalNotation do
|
|
201
184
|
[oo(1,3), oo(4,5)] => IntervalSet.new([OpenOpenInterval.new(1,3), OpenOpenInterval.new(4,5)]),
|
202
185
|
[oo(3,5), oo(1,3)] => IntervalSet.new([OpenOpenInterval.new(1,3), OpenOpenInterval.new(3,5)]),
|
203
186
|
[oo(4,5), oo(1,3)] => IntervalSet.new([OpenOpenInterval.new(1,3), OpenOpenInterval.new(4,5)]),
|
204
|
-
|
187
|
+
|
205
188
|
[oo(1,3), oo(3,5), cc(7,9)] => IntervalSet.new([OpenOpenInterval.new(1,3), OpenOpenInterval.new(3,5), ClosedClosedInterval.new(7,9)]),
|
206
189
|
[oo(1,3), oo(3,5), oo(5,7)] => IntervalSet.new([OpenOpenInterval.new(1,3), OpenOpenInterval.new(3,5), OpenOpenInterval.new(5,7)]),
|
207
190
|
[oo(1,3), cc(7,9), oo(3,5)] => IntervalSet.new([OpenOpenInterval.new(1,3), OpenOpenInterval.new(3,5), ClosedClosedInterval.new(7,9)]),
|
@@ -259,13 +242,13 @@ describe IntervalNotation do
|
|
259
242
|
[ge(3), co(1,3)] => ge(1),
|
260
243
|
[ge(3), oc(1,3)] => gt(1),
|
261
244
|
[ge(3), cc(1,3)] => ge(1),
|
262
|
-
|
245
|
+
|
263
246
|
[ge(3), oo(2,5)] => gt(2),
|
264
247
|
[ge(3), co(2,5)] => ge(2),
|
265
248
|
[ge(3), co(4,5)] => ge(3),
|
266
249
|
[ge(3), oo(4,5)] => ge(3),
|
267
250
|
[ge(3), oo(3,5)] => ge(3),
|
268
|
-
[ge(3), co(3,5)] => ge(3),
|
251
|
+
[ge(3), co(3,5)] => ge(3),
|
269
252
|
[ge(3), pt(2)] => IntervalSet.new([Point.new(2), ClosedOpenInterval.new(3, Float::INFINITY)]),
|
270
253
|
[ge(3), pt(3)] => ge(3),
|
271
254
|
[ge(3), pt(4)] => ge(3),
|
@@ -314,7 +297,7 @@ describe IntervalNotation do
|
|
314
297
|
}).merge({ # too long hash to be one hash
|
315
298
|
# non-adjacent
|
316
299
|
[oo(1,3), oo(5,6)] => IntervalSet.new([OpenOpenInterval.new(1, 3), OpenOpenInterval.new(5, 6)]),
|
317
|
-
|
300
|
+
|
318
301
|
# adjacent
|
319
302
|
[oo(1,3), oo(3,6)] => IntervalSet.new([OpenOpenInterval.new(1, 3), OpenOpenInterval.new(3, 6)]),
|
320
303
|
[oo(1,3), co(3,6)] => oo(1,6),
|
@@ -420,97 +403,6 @@ describe IntervalNotation do
|
|
420
403
|
end
|
421
404
|
end
|
422
405
|
|
423
|
-
|
424
|
-
describe '#intersection' do
|
425
|
-
{
|
426
|
-
[oo(1,3), oo(1,3)] => oo(1,3),
|
427
|
-
[oo(1,3), oc(1,3)] => oo(1,3),
|
428
|
-
[oo(1,3), co(1,3)] => oo(1,3),
|
429
|
-
[oo(1,3), cc(1,3)] => oo(1,3),
|
430
|
-
|
431
|
-
[pt(2), pt(3)] => Empty,
|
432
|
-
[pt(3), pt(3)] => pt(3),
|
433
|
-
[pt(3)|pt(5), pt(3)|pt(5)] => pt(3)|pt(5),
|
434
|
-
[pt(3)|pt(5), pt(3)|pt(7)] => pt(3),
|
435
|
-
|
436
|
-
[oo(1,3)|oo(5,7), oo(1,3)|oo(5,7)] => oo(1,3)|oo(5,7),
|
437
|
-
[oo(1,3)|co(5,7), oo(1,3)|oo(5,7)] => oo(1,3)|oo(5,7),
|
438
|
-
[oo(1,3)|oo(5,7), oo(1,3)|co(5,7)] => oo(1,3)|oo(5,7),
|
439
|
-
[oo(1,3)|co(5,7), oo(1,3)|co(5,7)] => oo(1,3)|co(5,7),
|
440
|
-
|
441
|
-
[oo(1,3)|oo(3,5), oo(1,3)] => oo(1,3),
|
442
|
-
[oo(1,3)|oo(3,5), oo(1,3)|oo(7,9)] => oo(1,3),
|
443
|
-
[oo(1,3)|oo(3,5), oo(3,5)] => oo(3,5),
|
444
|
-
[oo(1,3)|oo(3,5), oo(1,3)|oo(3,5)] => oo(1,3)|oo(3,5),
|
445
|
-
|
446
|
-
[oo(1,3), oo(4,5)] => Empty,
|
447
|
-
[oo(1,3), oo(3,5)] => Empty,
|
448
|
-
[oo(1,3), co(3,5)] => Empty,
|
449
|
-
[oc(1,3), oo(3,5)] => Empty,
|
450
|
-
[oc(1,3), co(3,5)] => pt(3),
|
451
|
-
|
452
|
-
[oo(1,3), oo(0,5)] => oo(1,3),
|
453
|
-
[oc(1,3), oo(0,5)] => oc(1,3),
|
454
|
-
[co(1,3), oo(0,5)] => co(1,3),
|
455
|
-
[cc(1,3), oo(0,5)] => cc(1,3),
|
456
|
-
|
457
|
-
[oo(1,3), oo(1,5)] => oo(1,3),
|
458
|
-
[co(1,3), oo(1,5)] => oo(1,3),
|
459
|
-
[oc(1,3), oo(1,5)] => oc(1,3),
|
460
|
-
[cc(1,3), oo(1,5)] => oc(1,3),
|
461
|
-
[co(3,5), oo(1,5)] => co(3,5),
|
462
|
-
|
463
|
-
[oo(1,3), oo(2,5)] => oo(2,3),
|
464
|
-
[oc(1,3), oo(2,5)] => oc(2,3),
|
465
|
-
[oo(1,3), co(2,5)] => co(2,3),
|
466
|
-
[oc(1,3), co(2,5)] => cc(2,3),
|
467
|
-
|
468
|
-
[oo(1,3), pt(2)] => pt(2),
|
469
|
-
[oo(1,3), pt(3)] => Empty,
|
470
|
-
[oc(1,3), pt(3)] => pt(3),
|
471
|
-
[oo(1,3), pt(4)] => Empty,
|
472
|
-
[oc(1,3), pt(4)] => Empty,
|
473
|
-
|
474
|
-
[oo(1,3)|oo(3,5), oo(2,3)|oo(3,7)] => oo(2,3)|oo(3,5),
|
475
|
-
[oo(1,3)|oo(3,5), oo(2,7)] => oo(2,3)|oo(3,5),
|
476
|
-
|
477
|
-
[oo(2,6), oo(1,3)|co(5,7)] => oo(2,3)|co(5,6),
|
478
|
-
[oo(1,6), oo(1,3)|co(5,7)] => oo(1,3)|co(5,6),
|
479
|
-
[oo(0,6), oo(1,3)|co(5,7)] => oo(1,3)|co(5,6),
|
480
|
-
[oo(0,6), oo(1,3)|co(5,6)] => oo(1,3)|co(5,6),
|
481
|
-
|
482
|
-
|
483
|
-
}.each do |intervals, answer|
|
484
|
-
it "IntervalNotation::Operations.intersection(#{intervals.map(&:to_s).join(',')} should equal #{answer}" do
|
485
|
-
expect( IntervalNotation::Operations.intersection(intervals) ).to eq answer
|
486
|
-
end
|
487
|
-
|
488
|
-
it "IntervalNotation::Operations.intersection(#{intervals.map(&:to_s).join(',')} should equal consequent unite: #{intervals.map(&:to_s).join('&')}" do
|
489
|
-
expect( IntervalNotation::Operations.intersection(intervals) ).to eq intervals.inject(&:intersection)
|
490
|
-
end
|
491
|
-
|
492
|
-
if intervals.size == 2
|
493
|
-
interval_1, interval_2 = intervals
|
494
|
-
it "#{interval_1} & #{interval_2} should equal #{answer}" do
|
495
|
-
expect( interval_1.intersection(interval_2) ).to eq answer
|
496
|
-
end
|
497
|
-
end
|
498
|
-
end
|
499
|
-
|
500
|
-
{
|
501
|
-
[oo(1,3), oo(1,3), oo(1,3)] => oo(1,3),
|
502
|
-
[oo(1,3), cc(1,3), oo(1,3)] => oo(1,3),
|
503
|
-
[oo(1,3), cc(0,5), cc(1,3)] => oo(1,3),
|
504
|
-
[cc(1,5), cc(3,7), cc(1,3)|cc(5,7)] => pt(3)|pt(5),
|
505
|
-
[oo(1,5), oo(3,7), oo(1,3)|oo(5,7)] => Empty,
|
506
|
-
[oo(1,5), oc(1,3), co(3,5)] => pt(3)
|
507
|
-
}.each do |intervals, answer|
|
508
|
-
it "#{intervals.map(&:to_s).join('&')} should equal #{answer}" do
|
509
|
-
expect( IntervalNotation::Operations.intersection(intervals) ).to eq answer
|
510
|
-
end
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
406
|
describe '#subtract' do
|
515
407
|
{
|
516
408
|
[oo(1,5)|oo(6,8),(oo(1,5))] => oo(6,8),
|
@@ -663,7 +555,7 @@ describe IntervalNotation do
|
|
663
555
|
end
|
664
556
|
end
|
665
557
|
|
666
|
-
describe '#contain? / contained_by?' do
|
558
|
+
describe '#contain? / contained_by?' do
|
667
559
|
{
|
668
560
|
[oo(1,3), oo(1,2)] => true,
|
669
561
|
[oo(1,3), oo(1.5,2.5)] => true,
|
@@ -700,53 +592,13 @@ describe IntervalNotation do
|
|
700
592
|
end
|
701
593
|
end
|
702
594
|
|
703
|
-
describe '#intersect?' do
|
704
|
-
{
|
705
|
-
[oo(1,3), oo(1,2)] => true,
|
706
|
-
[oo(1,3), oo(1,3)] => true,
|
707
|
-
[oo(1,3), cc(1.5,2.5)] => true,
|
708
|
-
|
709
|
-
[oo(1,3), oo(3,4)] => false,
|
710
|
-
[oc(1,3), oo(3,4)] => false,
|
711
|
-
[oo(1,3), co(3,4)] => false,
|
712
|
-
[oc(1,3), co(3,4)] => true,
|
713
|
-
|
714
|
-
[oo(1,3), oo(4,5)] => false,
|
715
|
-
[cc(1,3), cc(4,5)] => false,
|
716
|
-
[cc(1,3), pt(2)|cc(4,5)] => true,
|
717
|
-
[co(1,3), pt(2)|cc(4,5)] => true,
|
718
|
-
[cc(1,3), pt(3)|cc(4,5)] => true,
|
719
|
-
[co(1,3), pt(3)|cc(4,5)] => false,
|
720
|
-
[cc(1,3), cc(4,5)|pt(6)] => false,
|
721
595
|
|
722
|
-
[lt(10), oo(1,3)] => true,
|
723
|
-
[lt(10), oo(11,12)] => false,
|
724
|
-
[lt(10), oo(10,11)] => false,
|
725
|
-
[le(10), co(10,11)] => true,
|
726
|
-
[le(10), le(9)] => true,
|
727
|
-
[le(10), le(11)] => true,
|
728
|
-
[le(10), ge(11)] => false,
|
729
|
-
[le(10), ge(10)] => true,
|
730
|
-
[le(10), gt(10)] => false,
|
731
|
-
[le(10), gt(11)] => false,
|
732
|
-
}.each do |(interval_1, interval_2), answer|
|
733
|
-
if answer
|
734
|
-
it "#{interval_1}.intersect?(#{interval_2}) should be truthy" do
|
735
|
-
expect( interval_1.intersect?(interval_2) ).to be_truthy
|
736
|
-
end
|
737
|
-
else
|
738
|
-
it "#{interval_1}.intersect?(#{interval_2}) should be falsy" do
|
739
|
-
expect( interval_1.intersect?(interval_2) ).to be_falsy
|
740
|
-
end
|
741
|
-
end
|
742
|
-
end
|
743
|
-
end
|
744
596
|
|
745
597
|
describe '#contiguous?' do
|
746
598
|
it 'Empty interval is treated as contiguous' do
|
747
599
|
expect(Empty).to be_contiguous
|
748
600
|
end
|
749
|
-
|
601
|
+
|
750
602
|
it 'Single component intervals are treated as contiguous' do
|
751
603
|
expect(R).to be_contiguous
|
752
604
|
expect(oo(1,3)).to be_contiguous
|
data/spec/spec_helper.rb
CHANGED
@@ -1 +1,36 @@
|
|
1
1
|
require 'rspec'
|
2
|
+
|
3
|
+
require_relative '../lib/interval_notation'
|
4
|
+
|
5
|
+
include IntervalNotation
|
6
|
+
include IntervalNotation::BasicIntervals
|
7
|
+
include IntervalNotation::Syntax::Short
|
8
|
+
|
9
|
+
def interval_for_each_boundary_type(from, to)
|
10
|
+
return enum_for(:interval_for_each_boundary_type, from, to) unless block_given?
|
11
|
+
[true, false].product([true,false]).each do |include_from, include_to|
|
12
|
+
yield BasicIntervals.interval_by_boundary_inclusion(include_from, from, include_to, to).to_interval_set
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def pairs_of_intervals_for_each_boundary_with_answer(from_1, to_1, from_2, to_2, answer)
|
18
|
+
interval_for_each_boundary_type(from_1, to_1).flat_map { |interval_1|
|
19
|
+
interval_for_each_boundary_type(from_2, to_2).map { |interval_2|
|
20
|
+
[[interval_1, interval_2], answer]
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def each_combination_of_intervals(intervals)
|
26
|
+
basic_intervals = intervals.flat_map(&:intervals)
|
27
|
+
(1..basic_intervals.size / 2).each do |chunk_1_size|
|
28
|
+
indices = basic_intervals.size.times.to_a
|
29
|
+
indices.combination(chunk_1_size).each do |chunk_1_indices|
|
30
|
+
chunk_2_indices = indices - chunk_1_indices
|
31
|
+
chunk_1 = IntervalNotation::Operations.union(chunk_1_indices.map{|i| IntervalSet.new([basic_intervals[i]]) })
|
32
|
+
chunk_2 = IntervalNotation::Operations.union(chunk_2_indices.map{|i| IntervalSet.new([basic_intervals[i]]) })
|
33
|
+
yield chunk_1, chunk_2
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interval_notation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Vorontsov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- README.md
|
53
53
|
- Rakefile
|
54
54
|
- TODO.md
|
55
|
+
- benchmark/benchmark.rb
|
55
56
|
- interval_notation.gemspec
|
56
57
|
- lib/interval_notation.rb
|
57
58
|
- lib/interval_notation/basic_intervals.rb
|
@@ -61,6 +62,7 @@ files:
|
|
61
62
|
- lib/interval_notation/operations.rb
|
62
63
|
- lib/interval_notation/version.rb
|
63
64
|
- spec/basic_intervals_spec.rb
|
65
|
+
- spec/intersection_spec.rb
|
64
66
|
- spec/interval_notation_spec.rb
|
65
67
|
- spec/spec_helper.rb
|
66
68
|
homepage: https://github.com/prijutme4ty/interval_notation
|
@@ -89,5 +91,6 @@ specification_version: 4
|
|
89
91
|
summary: interval_notation allows one to work with 1D-intervals.
|
90
92
|
test_files:
|
91
93
|
- spec/basic_intervals_spec.rb
|
94
|
+
- spec/intersection_spec.rb
|
92
95
|
- spec/interval_notation_spec.rb
|
93
96
|
- spec/spec_helper.rb
|