interval_notation 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/README.md +163 -0
- data/Rakefile +2 -0
- data/TODO.md +3 -0
- data/interval_notation.gemspec +23 -0
- data/lib/interval_notation/basic_intervals.rb +263 -0
- data/lib/interval_notation/combiners.rb +120 -0
- data/lib/interval_notation/error.rb +3 -0
- data/lib/interval_notation/interval_set.rb +190 -0
- data/lib/interval_notation/operations.rb +62 -0
- data/lib/interval_notation/version.rb +3 -0
- data/lib/interval_notation.rb +117 -0
- data/spec/basic_intervals_spec.rb +140 -0
- data/spec/interval_notation_spec.rb +881 -0
- data/spec/spec_helper.rb +1 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a937def556100b39f5ceda9350746e0bd4d8ecb7
|
4
|
+
data.tar.gz: 41f639a8c41a16cbecb65dce7bfa8862429196da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ef63264b1222cc9014f420b3dcf35528dec369db7d2515ac6ff7fa66e96714ec1b091e51a8d7c77c5dd9052b2856d6332adcf92c9a117f71bd67bb1dcbb7ac27
|
7
|
+
data.tar.gz: f453f8d84ed5900c06713ede45ef045e0fe562ab8e469e4522db0be2daedb4aad1c593dd1f3970d5cdc40793942ea40ef412a809bd325a3f1e27e95894f2b566
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# IntervalNotation
|
2
|
+
|
3
|
+
`interval_notation` allows one to work with 1D-intervals.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'interval_notation'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install interval_notation
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
`IntervalNotation` provides methods to create intervals with open or closed boundaries or singular points, unite and intersect them, check inclusion into an interval and so on.
|
24
|
+
|
25
|
+
In order to construct intervals and interval sets, please use factory methods, not class constructors:
|
26
|
+
```ruby
|
27
|
+
include IntervalNotation::Syntax::Short
|
28
|
+
# Predefined interval sets
|
29
|
+
R # => (-∞,+∞)
|
30
|
+
Empty # => ∅
|
31
|
+
# Singular point
|
32
|
+
pi = pt(Math::PI) # => {3.141592653589793}
|
33
|
+
# Finite intervals
|
34
|
+
interval_1 = oo(1,3) # => (1,3)
|
35
|
+
interval_2 = oc(3,5) # => (3,5]
|
36
|
+
interval_3 = co(10,15) # => [10,15)
|
37
|
+
interval_4 = cc(4,11) # => [4,11]
|
38
|
+
# Aliases for infinite intervals
|
39
|
+
interval_5 = lt(7) # => (-∞,7)
|
40
|
+
interval_6 = le(-3) # => (-∞,-3]
|
41
|
+
interval_7 = gt(-3) # => (-3,+∞)
|
42
|
+
interval_8 = ge(5.5) # => [5.5,+∞)
|
43
|
+
# one can also create infinite intervals using basic methods
|
44
|
+
interval_5_2 = oo(-Float::INFINITY, 7) # => (-∞,7)
|
45
|
+
interval_6_2 = oc(-Float::INFINITY, -3) # => (-∞,-3]
|
46
|
+
interval_7_2 = oo(-3, Float::INFINITY) # => (-3,+∞)
|
47
|
+
interval_8_2 = co(5.5, Float::INFINITY) # => [5.5,+∞)
|
48
|
+
# Create interval set from string (see IntervalSet.from_string for details)
|
49
|
+
interval_9 = int('{0}U[1,5)U(5,infty)') # => {0}U[1,5)U(5,+∞)
|
50
|
+
```
|
51
|
+
|
52
|
+
If you prefer more descriptive method names, use `IntervalNotation::Syntax::Long`. In such case you'll have `open_open`, `open_closed`, `closed_open`, `closed_closed`, `less_than`, `less_than_or_equal_to`, `greater_than`, `greater_than_or_equal_to` and `point` methods. `interval` is a long-form analog for `int` - to create interval set from string
|
53
|
+
|
54
|
+
Consider that no one class is supposed to be used directly! For further details see section **Internal structure**.
|
55
|
+
|
56
|
+
### Interval operations
|
57
|
+
Intervals can be combined in many different ways:
|
58
|
+
```ruby
|
59
|
+
include IntervalNotation::Syntax::Long
|
60
|
+
a = open_closed(0,15) # => (0,15]
|
61
|
+
b = closed_open(10,25) # => [10,25)
|
62
|
+
c = point(-5) # => {-5}
|
63
|
+
bc = b | c # => {-5}∪[10,25)
|
64
|
+
|
65
|
+
# Union of a pair of intervals:
|
66
|
+
a | b # => (0,25)
|
67
|
+
a.union(b) # ditto
|
68
|
+
|
69
|
+
# Intersection:
|
70
|
+
a & b # => [10,15]
|
71
|
+
a.intersection(b)
|
72
|
+
|
73
|
+
# Difference:
|
74
|
+
a - b # => (0,10)
|
75
|
+
a.subtract(b)
|
76
|
+
|
77
|
+
# Symmetric difference:
|
78
|
+
a ^ b # => (0,10)∪(15,25)
|
79
|
+
a.symmetric_difference(b)
|
80
|
+
|
81
|
+
# Interval complement
|
82
|
+
~a # => (-∞,0]∪(15,+∞)
|
83
|
+
a.complement
|
84
|
+
|
85
|
+
# Interval closure
|
86
|
+
bc.closure # => {-5}∪[10,25]
|
87
|
+
|
88
|
+
# Covering interval
|
89
|
+
bc.covering_interval # => [-5,25)
|
90
|
+
```
|
91
|
+
|
92
|
+
If you want to combine more than two intervals, you can perform several consequent operations:
|
93
|
+
```ruby
|
94
|
+
a | b | c # => {-5}∪(0,25)
|
95
|
+
a & b & c # => ∅
|
96
|
+
# or may be
|
97
|
+
[a,b,c].inject(&:|) # => {-5}∪(0,25)
|
98
|
+
[a,b,c].inject(&:&) # => ∅
|
99
|
+
```
|
100
|
+
But there is a much better and faster way to unite or intersect multiple intervals:
|
101
|
+
```ruby
|
102
|
+
IntervalNotation::Operations.union([a,b,c]) # => {-5}∪(0,25]
|
103
|
+
IntervalNotation::Operations.intersection([a,b,c]) # => ∅
|
104
|
+
```
|
105
|
+
If you unite thousands or millions of intervals, you definitely should choose the last method! Do not try to inject intervals one by one for the sake of perfomance. Running time can differ dramatically (seconds vs hours for union of hundreds of thousands intervals).
|
106
|
+
|
107
|
+
### Interval queries
|
108
|
+
One can test whether two intervals intersect, cover one another and so on:
|
109
|
+
```ruby
|
110
|
+
Empty.empty? # => true
|
111
|
+
closed_closed(0,5).empty? # => false
|
112
|
+
|
113
|
+
Empty.contiguous? # => true
|
114
|
+
closed_closed(0,5).contiguous? # => true
|
115
|
+
point(8).contiguous? # => true
|
116
|
+
(open_open(0,5) | point(8)).contiguous? # => false
|
117
|
+
|
118
|
+
closed_closed(0,5).include_position?(3) # => true
|
119
|
+
open_open(0,5).include_position?(5) # => false
|
120
|
+
open_open(0,5).include_position?(8) # => false (actually nil which is falsy)
|
121
|
+
(open_open(0,5)|open_open(7,9)).include_position?(8)# => true
|
122
|
+
|
123
|
+
closed_closed(0,5).intersect?(closed_closed(3,10)) # => true
|
124
|
+
closed_closed(0,5).intersect?(closed_closed(5,10)) # => true
|
125
|
+
closed_closed(0,5).intersect?(open_closed(5,10)) # => false
|
126
|
+
closed_closed(0,5).intersect?(closed_closed(7,10)) # => false
|
127
|
+
|
128
|
+
closed_closed(0,5).contain?(closed_closed(2,3)) # => true
|
129
|
+
closed_closed(2,3).contained_by?(closed_closed(0,5)) # => true
|
130
|
+
```
|
131
|
+
|
132
|
+
Full list of querying methods:
|
133
|
+
```ruby
|
134
|
+
interval_set.total_length
|
135
|
+
interval_set.num_connected_components
|
136
|
+
interval_set.empty?
|
137
|
+
interval_set.contiguous?
|
138
|
+
interval_set.include_position?(position)
|
139
|
+
interval_set.intersect?(interval_set)
|
140
|
+
interval_set.contain?(interval_set)
|
141
|
+
interval_set.contained_by?(interval_set)
|
142
|
+
```
|
143
|
+
|
144
|
+
## Internal structure
|
145
|
+
|
146
|
+
`IntervalNotation::IntervalSet` is designed in order to keep ordered list of non-overlapping intervals and represent 1-D point set. Each interval in the `IntervalSet` is an instance of one of following classes: `Point`, `OpenOpenInterval`, `OpenClosedInterval`, `ClosedOpenInterval` or `ClosedOpenInterval` representing contiguous 1-D subsets. One can find them in `IntervalNotation::BasicIntervals` module. None of these classes is intended to be directly instantiated, usually intervals are constructed using factory methods and combining operations.
|
147
|
+
|
148
|
+
All factory methods listed above create `IntervalSet`s, wrapping an instance of corresponding interval or point class. All interval set operations create new `IntervalSet`s, even if they contain the only basic interval.
|
149
|
+
|
150
|
+
`IntervalSet`s are value objects. Once instantiated they cannot be changed, all operations just create new objects. It also means, you can fearlessly use them as key values in hashes.
|
151
|
+
|
152
|
+
Combining of intervals is made by sweep line method, so is linear by number of intervals. Many querying operations (such as `#intersect`) rely on combining intervals thus also have linear complexity. Some of these perfomance drawbacks will be fixed in future.
|
153
|
+
Query `#include_position?` is made by binary search (so has logarithmic complexity).
|
154
|
+
|
155
|
+
`IntervalSet` has two constructors: `.new` and `.new_unsafe`. Use them with caution. Default constructor accepts data in a specially prepared format, while unsafe constructor makes no validation at all (and can create inconsistent obect). But `.new_unsafe` still can be useful when you are absolutely sure, provided data is ok, and a few milliseconds for unnecessary validation make sense.
|
156
|
+
|
157
|
+
## Contributing
|
158
|
+
|
159
|
+
1. Fork it ( https://github.com/[my-github-username]/interval_notation/fork )
|
160
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
161
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
162
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
163
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/TODO.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'interval_notation/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "interval_notation"
|
8
|
+
spec.version = IntervalNotation::VERSION
|
9
|
+
spec.authors = ["Ilya Vorontsov"]
|
10
|
+
spec.email = ["prijutme4ty@gmail.com"]
|
11
|
+
spec.summary = %q{interval_notation allows one to work with 1D-intervals.}
|
12
|
+
spec.description = %q{interval_notation provides methods to create intervals with open or closed boundaries or singular points, unite and intersect them, check inclusion into an interval and so on.}
|
13
|
+
spec.homepage = "https://github.com/prijutme4ty/interval_notation"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
require_relative 'error'
|
2
|
+
require_relative 'interval_set'
|
3
|
+
|
4
|
+
module IntervalNotation
|
5
|
+
module BasicIntervals # :nodoc: all
|
6
|
+
# Auxiliary class to represent information about interval boundaries
|
7
|
+
BoundaryPoint = Struct.new(:value, :included, :opening, :interval_index, :interval_boundary) do
|
8
|
+
def closing
|
9
|
+
!opening
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ActslikeInterval
|
14
|
+
def self.included(base)
|
15
|
+
base.class_eval do
|
16
|
+
attr_reader :from, :to
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def closure
|
21
|
+
if from_finite?
|
22
|
+
if to_finite?
|
23
|
+
ClosedClosedInterval.new(from, to)
|
24
|
+
else
|
25
|
+
ClosedOpenInterval.new(from, to) # to == +∞
|
26
|
+
end
|
27
|
+
else
|
28
|
+
if to_finite?
|
29
|
+
OpenClosedInterval.new(from, to) # from == -∞
|
30
|
+
else
|
31
|
+
OpenOpenInterval.new(from, to) # from == -∞, to == +∞
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_interval_set; IntervalSet.new([self]); end
|
37
|
+
|
38
|
+
def from_finite?; from.to_f.finite?; end
|
39
|
+
def to_finite?; to.to_f.finite?; end
|
40
|
+
def finite?; from_finite? && to_finite?; end
|
41
|
+
|
42
|
+
def from_to_s; from_finite? ? from : MINUS_INFINITY_SYMBOL; end
|
43
|
+
def to_to_s; to_finite? ? to : PLUS_INFINITY_SYMBOL; end
|
44
|
+
|
45
|
+
def length; to - from; end
|
46
|
+
def inspect; to_s; end
|
47
|
+
def singular_point?; false; end
|
48
|
+
def hash; [@from, @to, include_from?, include_to?].hash; end;
|
49
|
+
def eql?(other); other.class.equal?(self.class) && from.eql?(other.from) && to.eql?(other.to); end
|
50
|
+
end
|
51
|
+
|
52
|
+
class OpenOpenInterval
|
53
|
+
include ActslikeInterval
|
54
|
+
def initialize(from, to)
|
55
|
+
raise Error, "Interval (#{from};#{to}) can't be created" unless from < to
|
56
|
+
@from = from
|
57
|
+
@to = to
|
58
|
+
end
|
59
|
+
def to_s; "(#{from_to_s};#{to_to_s})"; end
|
60
|
+
def include_from?; false; end
|
61
|
+
def include_to?; false; end
|
62
|
+
def include_position?(value); from < value && value < to; end
|
63
|
+
def ==(other); other.is_a?(OpenOpenInterval) && from == other.from && to == other.to; end
|
64
|
+
def interval_boundaries(interval_index)
|
65
|
+
[ BoundaryPoint.new(from, false, true, interval_index, true),
|
66
|
+
BoundaryPoint.new(to, false, false, interval_index, true) ]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class OpenClosedInterval
|
71
|
+
include ActslikeInterval
|
72
|
+
def initialize(from, to)
|
73
|
+
raise Error, "Interval (#{from};#{to}] can't be created" unless from < to
|
74
|
+
raise Error, "Infinite boundary should be open" unless to.to_f.finite?
|
75
|
+
@from = from
|
76
|
+
@to = to
|
77
|
+
end
|
78
|
+
def to_s; "(#{from_to_s};#{to_to_s}]"; end
|
79
|
+
def include_from?; false; end
|
80
|
+
def include_to?; true; end
|
81
|
+
|
82
|
+
def to_finite?; true; end
|
83
|
+
def to_to_s; to; end
|
84
|
+
|
85
|
+
def closure
|
86
|
+
if from_finite?
|
87
|
+
ClosedClosedInterval.new(from, to)
|
88
|
+
else
|
89
|
+
OpenClosedInterval.new(from, to) # from == -∞
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def include_position?(value); from < value && value <= to; end
|
94
|
+
def ==(other); other.is_a?(OpenClosedInterval) && from == other.from && to == other.to; end
|
95
|
+
def interval_boundaries(interval_index)
|
96
|
+
[ BoundaryPoint.new(from, false, true, interval_index, true),
|
97
|
+
BoundaryPoint.new(to, true, false, interval_index, true) ]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class ClosedOpenInterval
|
102
|
+
include ActslikeInterval
|
103
|
+
def initialize(from, to)
|
104
|
+
raise Error, "Interval [#{from};#{to}) can't be created" unless from < to
|
105
|
+
raise Error, "Infinite boundary should be open" unless from.to_f.finite?
|
106
|
+
@from = from
|
107
|
+
@to = to
|
108
|
+
end
|
109
|
+
def to_s; "[#{from_to_s};#{to_to_s})"; end
|
110
|
+
def include_from?; true; end
|
111
|
+
def include_to?; false; end
|
112
|
+
|
113
|
+
def from_finite?; true; end
|
114
|
+
def from_to_s; from; end
|
115
|
+
|
116
|
+
def closure
|
117
|
+
if to_finite?
|
118
|
+
ClosedClosedInterval.new(from, to)
|
119
|
+
else
|
120
|
+
ClosedOpenInterval.new(from, to) # to == +∞
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def include_position?(value); from <= value && value < to; end
|
125
|
+
def ==(other); other.is_a?(ClosedOpenInterval) && from == other.from && to == other.to; end
|
126
|
+
def interval_boundaries(interval_index)
|
127
|
+
[ BoundaryPoint.new(from, true, true, interval_index, true),
|
128
|
+
BoundaryPoint.new(to, false, false, interval_index, true) ]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class ClosedClosedInterval
|
133
|
+
include ActslikeInterval
|
134
|
+
def initialize(from, to)
|
135
|
+
raise Error, "Interval [#{from};#{to}] can't be created" unless from < to
|
136
|
+
raise Error, "Infinite boundary should be open" unless from.to_f.finite? && to.to_f.finite?
|
137
|
+
@from = from
|
138
|
+
@to = to
|
139
|
+
end
|
140
|
+
def to_s; "[#{from_to_s};#{to_to_s}]"; end
|
141
|
+
def include_from?; true; end
|
142
|
+
def include_to?; true; end
|
143
|
+
|
144
|
+
def from_finite?; true; end
|
145
|
+
def to_finite?; true; end
|
146
|
+
def finite?; true; end
|
147
|
+
def from_to_s; from; end
|
148
|
+
def to_to_s; to; end
|
149
|
+
|
150
|
+
def closure; self; end
|
151
|
+
|
152
|
+
def include_position?(value); from <= value && value <= to; end
|
153
|
+
def ==(other); other.is_a?(ClosedClosedInterval) && from == other.from && to == other.to; end
|
154
|
+
def interval_boundaries(interval_index)
|
155
|
+
[ BoundaryPoint.new(from, true, true, interval_index, true),
|
156
|
+
BoundaryPoint.new(to, true, false, interval_index, true) ]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Point
|
161
|
+
attr_reader :value
|
162
|
+
protected :value
|
163
|
+
def initialize(value)
|
164
|
+
raise Error, "Point can't represent an infinity" unless value.to_f.finite?
|
165
|
+
@value = value
|
166
|
+
end
|
167
|
+
def from; value; end
|
168
|
+
def to; value; end
|
169
|
+
|
170
|
+
def to_interval_set; IntervalSet.new([self]); end
|
171
|
+
|
172
|
+
def from_finite?; true; end
|
173
|
+
def to_finite?; true; end
|
174
|
+
def finite?; true; end
|
175
|
+
|
176
|
+
def length; 0; end
|
177
|
+
def to_s; "{#{@value}}"; end
|
178
|
+
def inspect; to_s; end
|
179
|
+
|
180
|
+
def include_from?; true; end
|
181
|
+
def include_to?; true; end
|
182
|
+
|
183
|
+
def closure; self; end
|
184
|
+
|
185
|
+
def singular_point?; true; end
|
186
|
+
def include_position?(val); value == val; end
|
187
|
+
def hash; @value.hash; end;
|
188
|
+
def eql?(other); other.class.equal?(self.class) && value == other.value; end
|
189
|
+
def ==(other); other.is_a?(Point) && value == other.value; end
|
190
|
+
def interval_boundaries(interval_index)
|
191
|
+
BoundaryPoint.new(from, true, nil, interval_index, false)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.interval_by_boundary_inclusion(include_from, from, include_to, to)
|
196
|
+
if include_from
|
197
|
+
if include_to
|
198
|
+
if from != to
|
199
|
+
ClosedClosedInterval.new(from, to)
|
200
|
+
else
|
201
|
+
Point.new(from)
|
202
|
+
end
|
203
|
+
else
|
204
|
+
ClosedOpenInterval.new(from, to)
|
205
|
+
end
|
206
|
+
else
|
207
|
+
if include_to
|
208
|
+
OpenClosedInterval.new(from, to)
|
209
|
+
else
|
210
|
+
OpenOpenInterval.new(from, to)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
PLUS_INFINITY_VARIANTS = ['∞', 'inf', 'infinity', 'infty', '\infty', '+∞', '+inf', '+infinity', '+infty', '+\infty']
|
216
|
+
MINUS_INFINITY_VARIANTS = ['-∞', '-inf', '-infinity', '-infty', '-\infty']
|
217
|
+
EMPTY_VARIANTS = ['∅','empty','']
|
218
|
+
OPENING_VARIANTS = ['(','[']
|
219
|
+
CLOSING_VARIANTS = [')',']']
|
220
|
+
|
221
|
+
# returns an Interval (wrapped or unwrapped) or an array of Points or empty list (for Empty interval)
|
222
|
+
def self.from_string(interval_str)
|
223
|
+
interval_str = interval_str.gsub(/\s/,'')
|
224
|
+
return Empty if EMPTY_VARIANTS.include?(interval_str.downcase)
|
225
|
+
return R if interval_str == 'R'
|
226
|
+
if interval_str[0] == '{' && interval_str[-1] == '}'
|
227
|
+
interval_str[1..-2].split(/[,;]/).map{|el| Point.new(Float(el)) }
|
228
|
+
elsif OPENING_VARIANTS.include?(interval_str[0]) && CLOSING_VARIANTS.include?(interval_str[-1])
|
229
|
+
boundary_values = interval_str[1..-2].split(/[,;]/).map(&:strip)
|
230
|
+
raise Error, 'Unknown format' unless boundary_values.size == 2
|
231
|
+
from = (MINUS_INFINITY_VARIANTS.include?(boundary_values[0].downcase)) ? -Float::INFINITY : Float(boundary_values[0])
|
232
|
+
to = (PLUS_INFINITY_VARIANTS.include?(boundary_values[1].downcase)) ? Float::INFINITY : Float(boundary_values[1])
|
233
|
+
|
234
|
+
if interval_str[0] == '('
|
235
|
+
if interval_str[-1] == ')'
|
236
|
+
OpenOpenInterval.new(from, to)
|
237
|
+
elsif interval_str[-1] == ']'
|
238
|
+
OpenClosedInterval.new(from, to)
|
239
|
+
else
|
240
|
+
raise Error, 'Unknown format'
|
241
|
+
end
|
242
|
+
elsif interval_str[0] == '['
|
243
|
+
if interval_str[-1] == ')'
|
244
|
+
ClosedOpenInterval.new(from, to)
|
245
|
+
elsif interval_str[-1] == ']'
|
246
|
+
ClosedClosedInterval.new(from, to)
|
247
|
+
else
|
248
|
+
raise Error, 'Unknown format'
|
249
|
+
end
|
250
|
+
else
|
251
|
+
raise Error, 'Unknown format'
|
252
|
+
end
|
253
|
+
else
|
254
|
+
begin
|
255
|
+
Point.new(Float(interval_str))
|
256
|
+
rescue
|
257
|
+
raise Error, 'Unknown format'
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module IntervalNotation
|
4
|
+
# Combiner is an internal helper class for combining interval sets using sweep line.
|
5
|
+
# It starts moving from -∞ to +∞ and keep which intervals are crossed by sweep line.
|
6
|
+
# Class helps to effectively recalculate number of crossed intervals without rechecking
|
7
|
+
# all intervals each time, and dramatically reduces speed of operations on large number of intervals
|
8
|
+
class Combiner
|
9
|
+
attr_reader :num_interval_sets
|
10
|
+
attr_reader :previous_state
|
11
|
+
|
12
|
+
def initialize(num_interval_sets)
|
13
|
+
@num_interval_sets = num_interval_sets
|
14
|
+
@inside = Array.new(num_interval_sets, false)
|
15
|
+
@num_intervals_inside = 0 # number of intervals, we are inside (for efficiency)
|
16
|
+
end
|
17
|
+
|
18
|
+
# When sweep line pass several interval boundaries, +#pass+ should get all those points at once
|
19
|
+
# and update status of crossing sweep line.
|
20
|
+
# It also stores previous state, because it's actively used downstream.
|
21
|
+
def pass(points_on_place)
|
22
|
+
@previous_state = state
|
23
|
+
pass_recalculation(points_on_place)
|
24
|
+
end
|
25
|
+
|
26
|
+
# See +#pass+ for details
|
27
|
+
def pass_recalculation(points_on_place) # :nodoc:
|
28
|
+
num_spanning_intervals = @num_intervals_inside
|
29
|
+
num_covering_boundaries = 0
|
30
|
+
|
31
|
+
points_on_place.each do |point|
|
32
|
+
num_covering_boundaries += 1 if point.included
|
33
|
+
|
34
|
+
if point.interval_boundary
|
35
|
+
num_spanning_intervals -= 1 if point.closing
|
36
|
+
@num_intervals_inside += (@inside[point.interval_index] ? -1 : 1)
|
37
|
+
@inside[point.interval_index] ^= true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@num_interval_sets_covering_last_point = num_spanning_intervals + num_covering_boundaries
|
41
|
+
end
|
42
|
+
private :pass_recalculation
|
43
|
+
end
|
44
|
+
|
45
|
+
class UnionCombiner < Combiner
|
46
|
+
# checks whether current section should be included
|
47
|
+
def state
|
48
|
+
@num_intervals_inside > 0
|
49
|
+
end
|
50
|
+
|
51
|
+
# checks whether last passed point should be included
|
52
|
+
def include_last_point
|
53
|
+
@num_interval_sets_covering_last_point > 0
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class IntersectCombiner < Combiner
|
58
|
+
# checks whether current section should be included
|
59
|
+
def state
|
60
|
+
@num_intervals_inside == num_interval_sets
|
61
|
+
end
|
62
|
+
|
63
|
+
# checks whether last passed point should be included
|
64
|
+
def include_last_point
|
65
|
+
@num_interval_sets_covering_last_point == num_interval_sets
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class SubtractCombiner < Combiner
|
70
|
+
# checks whether last passed point should be included
|
71
|
+
attr_reader :include_last_point
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
@include_last_point = nil
|
75
|
+
@inside = [false, false]
|
76
|
+
end
|
77
|
+
|
78
|
+
# checks whether current section should be included
|
79
|
+
def state
|
80
|
+
@inside[0] && ! @inside[1]
|
81
|
+
end
|
82
|
+
|
83
|
+
# See +#pass+ for details
|
84
|
+
def pass_recalculation(points_on_place) # :nodoc:
|
85
|
+
included = @inside.dup
|
86
|
+
points_on_place.each do |point|
|
87
|
+
@inside[point.interval_index] ^= point.interval_boundary # doesn't change on singular points
|
88
|
+
included[point.interval_index] = point.included
|
89
|
+
end
|
90
|
+
@include_last_point = included[0] && !included[1]
|
91
|
+
end
|
92
|
+
private :pass_recalculation
|
93
|
+
end
|
94
|
+
|
95
|
+
class SymmetricDifferenceCombiner < Combiner
|
96
|
+
# checks whether last passed point should be included
|
97
|
+
attr_reader :include_last_point
|
98
|
+
|
99
|
+
def initialize
|
100
|
+
@include_last_point = nil
|
101
|
+
@inside = [false, false]
|
102
|
+
end
|
103
|
+
|
104
|
+
# checks whether current section should be included
|
105
|
+
def state
|
106
|
+
@inside[0] ^ @inside[1]
|
107
|
+
end
|
108
|
+
|
109
|
+
# See +#pass+ for details
|
110
|
+
def pass_recalculation(points_on_place) # :nodoc:
|
111
|
+
included = @inside.dup
|
112
|
+
points_on_place.each do |point|
|
113
|
+
@inside[point.interval_index] ^= point.interval_boundary # doesn't change on singular points
|
114
|
+
included[point.interval_index] = point.included
|
115
|
+
end
|
116
|
+
@include_last_point = included[0] ^ included[1]
|
117
|
+
end
|
118
|
+
private :pass_recalculation
|
119
|
+
end
|
120
|
+
end
|