interval_notation 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,19 @@
1
+ for_article/
2
+ source_data/
3
+ source_data_2/
4
+ genome/
5
+ top_motif/
6
+ *.bed
7
+ robust_set.freeze1
8
+ robust_set.freeze1.reduced.pc-3
9
+ *.txt
10
+ *.out
11
+ *.log
12
+ *.sav
13
+ *.fasta
14
+ *.rar
15
+ *.tar
16
+ *.gz
17
+ *.csv
18
+ *.xls
19
+ *.xlsx
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in interval_notation.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/TODO.md ADDED
@@ -0,0 +1,3 @@
1
+ * Write benchmarks
2
+ * Optimize `#closure` and `#intersect?`
3
+ * Visualization in console and in IRuby (how to scale into 80-symbol or 1200px screen); SVG; TeX formatters
@@ -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
@@ -0,0 +1,3 @@
1
+ module IntervalNotation
2
+ class Error < StandardError; end
3
+ end