interval_notation 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5959995f95355a82acf74a537288c6625466975d
4
- data.tar.gz: b621dd95df2c290949cd919bc4e210444ff120f2
3
+ metadata.gz: 7c1800a973a0b109fedd3e4a0bc378af0f491c93
4
+ data.tar.gz: 224ceae1be9ff756b5fb5c0dd8fdc4688edd0585
5
5
  SHA512:
6
- metadata.gz: 6e4acb6c0fc3834c1b4ebd915356f2169212a53ff8fa4b0f6f3868f1e2555fa589433cf97a984b7dae89eb738ee1dc96453a3872b82ae33875290900834f7899
7
- data.tar.gz: 6fbc038e920099bde55ae847fa48b77ca9a1669c9bfa14507e3fa240ab73a6ecb52f7b9b8e846cd19397efc8976a7eccec7b67d31e846d40086dea64c377adef
6
+ metadata.gz: 4b18d4772d4f708e2a246ac71e0f17ca57e48885b3cc2e4845393fb1203d3279d08f993b5dde5be47079242100f0aa0d05a107ab6bfc23c14f8ae30f0f13d3d9
7
+ data.tar.gz: 0cf796a0a49792f1240e78413c95868cb8253a09cfd3ebbb65962b36f51885655b65fc057c4146f1e731e6157642c2b26619ff5788ed15d50a26b13068b7e7d2
data/README.md CHANGED
@@ -46,12 +46,15 @@ interval_6_2 = oc(-Float::INFINITY, -3) # => (-∞,-3]
46
46
  interval_7_2 = oo(-3, Float::INFINITY) # => (-3,+∞)
47
47
  interval_8_2 = co(5.5, Float::INFINITY) # => [5.5,+∞)
48
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,+∞)
49
+ interval_9 = interval('{0}U[1,5)U(5,infty)') # => {0}U[1,5)U(5,+∞)
50
50
  ```
51
51
 
52
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
53
 
54
- Consider that no one class is supposed to be used directly! For further details see section **Internal structure**.
54
+ Sometimes you want to create one of `BasicInterval`s which are underlying structures of `IntervalSet`s. In that case you can use similar constructors with `_basic` suffix like `oo_basic(from, to)` and so on.
55
+ Some constructors create not necessary contiguous intervals like `interval(String)` do. Such constructors do not have `*_basic` counterpart.
56
+
57
+ Note that `BasicInterval` classes are not supposed to be used for interval operations directly! For further details see section **Internal structure**.
55
58
 
56
59
  ### Interval operations
57
60
  Intervals can be combined in many different ways:
@@ -60,7 +63,9 @@ include IntervalNotation::Syntax::Long
60
63
  a = open_closed(0,15) # => (0,15]
61
64
  b = closed_open(10,25) # => [10,25)
62
65
  c = point(-5) # => {-5}
66
+ d = closed_closed(-200, -100) # => [-200,-100]
63
67
  bc = b | c # => {-5}∪[10,25)
68
+ bcd = b | c | d # => [-200,-100]∪{-5}∪[10,25)
64
69
 
65
70
  # Union of a pair of intervals:
66
71
  a | b # => (0,25)
@@ -87,6 +92,17 @@ bc.closure # => {-5}∪[10,25]
87
92
 
88
93
  # Covering interval
89
94
  bc.covering_interval # => [-5,25)
95
+
96
+ # Return connected components (contiguous non-adjacent intervals)
97
+ # comprising `IntervalSet` (and wrapped in `IntervalSet`s)
98
+ bc.connected_components # => [c, b]
99
+
100
+ # Return connected components (contiguous non-adjacent intervals)
101
+ # comprising `IntervalSet` (in `BasicInterval` representation; not wrapped)
102
+ bc.intervals # => [point_basic(-5), closed_open_basic(10,25)]
103
+
104
+ # Find connected component (BasicInterval representation) which covers a point
105
+ bc.interval_covering_point(12) # => [10,25)
90
106
  ```
91
107
 
92
108
  If you want to combine more than two intervals, you can perform several consequent operations:
@@ -97,7 +113,7 @@ a & b & c # => ∅
97
113
  [a,b,c].inject(&:|) # => {-5}∪(0,25)
98
114
  [a,b,c].inject(&:&) # => ∅
99
115
  ```
100
- But there is a much better and faster way to unite or intersect multiple intervals:
116
+ But there is a much faster way to unite or intersect multiple intervals:
101
117
  ```ruby
102
118
  IntervalNotation::Operations.union([a,b,c]) # => {-5}∪(0,25]
103
119
  IntervalNotation::Operations.intersection([a,b,c]) # => ∅
@@ -141,13 +157,85 @@ interval_set.contain?(interval_set)
141
157
  interval_set.contained_by?(interval_set)
142
158
  ```
143
159
 
160
+ ### Segmentation
161
+ Another essential structure in a library is a `Segmentation`. Segmentation is a partitioning of number line into adjacent non-ovelapping intervals (covering entire R) such that each segment has its own state. Adjacent segments with the same state are glued.
162
+ Segmentation can be used to trace lots of interval sets simultaneously. It's made with help of `Tagging` trace-states. `SingleTagging` and `MultiTagging` allow one to mark each interval set with its own tag and to partition number line into regions with certain tag sets.
163
+
164
+ ```ruby
165
+ intervals_tagged = {oo(0,10) => :A, cc(0,8) => :B, oo(5,15) => :C}
166
+ intervals_tagged_dup_tags = {oo(0,10) => :A, cc(0,8) => :B, oo(5,15) => :A}
167
+
168
+ ### Usual tagging returns segments with states which are `Set`s of tags:
169
+ IntervalNotation::SweepLine.make_tagging(intervals_tagged)
170
+ # => Segmentation: [<(-∞;0): {}>, <{0}: {B}>, <(0;5]: {A, B}>, <(5;8]: {A, B, C}>, <(8;10): {A, C}>, <[10;15): {C}>, <[15;+∞): {}>]
171
+
172
+ IntervalNotation::SweepLine.make_tagging(intervals_tagged_dup_tags)
173
+ # => Segmentation: [<(-∞;0): {}>, <{0}: {B}>, <(0;8]: {A, B}>, <(8;15): {A}>, <[15;+∞): {}>]
174
+
175
+ ### Multitagging returns segments with states which are `Hash`es of tag counts:
176
+ IntervalNotation::SweepLine.make_multitagging(intervals_tagged)
177
+ # => Segmentation: [<(-∞;0): {}>, <{0}: {:B=>1}>, <(0;5]: {:A=>1, :B=>1}>, <(5;8]: {:A=>1, :B=>1, :C=>1}>, <(8;10): {:A=>1, :C=>1}>, <[10;15): {:C=>1}>, <[15;+∞): {}>]
178
+
179
+ IntervalNotation::SweepLine.make_multitagging(intervals_tagged_dup_tags)
180
+ # => Segmentation: [<(-∞;0): {}>, <{0}: {:B=>1}>, <(0;5]: {:A=>1, :B=>1}>, <(5;8]: {:A=>2, :B=>1}>, <(8;10): {:A=>2}>, <[10;15): {:A=>1}>, <[15;+∞): {}>]
181
+ ```
182
+
183
+ One can create different segmentation states using `SweepLine.make_segmentation` with custom trace-state objects. Operations like union and intersection are made this way, using special trace-states which return true when sweep line intersect any/all of intervals. Trace-state is a special object which can recalculate state when interval boundaries were hit. See `SweepLine::TraceState` module for details.
184
+
185
+ `Segmentation` have some methods to transform states, query segments and so on:
186
+
187
+ ```ruby
188
+ include IntervalNotation
189
+ include IntervalNotation::Syntax::Short
190
+ segmentation = Segmentation.new([
191
+ Segmentation::Segment.new( lt_basic(0), Set.new ),
192
+ Segmentation::Segment.new( pt_basic(0), Set.new([:B]) ),
193
+ Segmentation::Segment.new( oc_basic(0,8), Set.new([:A,:B]) ),
194
+ Segmentation::Segment.new( oo_basic(8,15), Set.new([:A]) ),
195
+ Segmentation::Segment.new( ge_basic(15), Set.new ),
196
+ ])
197
+ # Segmentation: [<(-∞;0): {}>, <{0}: {B}>, <(0;8]: {A, B}>, <(8;15): {A}>, <[15;+∞): {}>]
198
+
199
+ ### `Segmentation#map_state` transforms state of each segment.
200
+ ### If necessary new segments will be glued
201
+ segmentation.map_state{|segment| segment.state.size }
202
+ # => Segmentation: [<(-∞;0): 0>, <{0}: 1>, <(0;8]: 2>, <(8;15): 1>, <[15;+∞): 0>]
203
+
204
+ segmentation.map_state{|segment| segment.state.size > 1 }
205
+ # => Segmentation: [<(-∞;0]: false>, <(0;8]: true>, <(8;+∞): false>]
206
+
207
+ ### `Segmentation#boolean_segmentation` transforms state of each segment into
208
+ ### boolean value (useful to glue segments which are truthy/falsy but
209
+ ### have not exactly equal state). Same result can be obtained by `#map_state`.
210
+ segmentation.boolean_segmentation{|segment| segment.state.size > 1 }
211
+ # => Segmentation: [<(-∞;0]: false>, <(0;8]: true>, <(8;+∞): false>]
212
+
213
+
214
+ ### `IntervalSet` and true/false `Segmentation` can be converted
215
+ ### to each other.
216
+ ### Use `IntervalSet#make_segmentation` and `Segmentation#make_interval_set`.
217
+ bool_segmentation = segmentation.boolean_segmentation{|segment|
218
+ segment.state.size > 1
219
+ }
220
+ bool_segmentation.make_interval_set
221
+ # => (0,8]
222
+
223
+ (oo(1,3) | pt(5)).make_segmentation
224
+ # => Segmentation: [<(-∞;1]: false>, <(1;3): true>, <[3;5): false>, <{5}: true>, <(5;+∞): false>]
225
+
226
+ ### `Segmentation#segment_covering_point` returns a `Segment`
227
+ ### which lies against specified point
228
+ segmentation.segment_covering_point(10)
229
+ # => <(8;15): {A}>
230
+ ```
231
+
144
232
  ## Internal structure
145
233
 
146
234
  `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
235
 
148
236
  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
237
 
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.
238
+ `IntervalSet`s and `Segmentation`s (as well as `BasicInterval`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
239
 
152
240
  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
241
  Query `#include_position?` is made by binary search (so has logarithmic complexity).
data/TODO.md CHANGED
@@ -5,3 +5,4 @@
5
5
  * What about working with simgle intervals (their, length, relations etc)?
6
6
  * (?) Make it possible to use intervals with non-numeric objects to be possible to use math expressions as interval boundaries
7
7
  * May be we should check that boundary is an actual number, not a NaN
8
+ * Make use of basic intervals (implement #coerce and make oo|co|pt|... return BasicIntervals)
@@ -2,7 +2,7 @@ require_relative 'interval_notation/version'
2
2
 
3
3
  require_relative 'interval_notation/error'
4
4
  require_relative 'interval_notation/basic_intervals'
5
- require_relative 'interval_notation/combiners'
5
+ require_relative 'interval_notation/sweep_line'
6
6
  require_relative 'interval_notation/interval_set'
7
7
  require_relative 'interval_notation/operations'
8
8
 
@@ -21,51 +21,62 @@ module IntervalNotation
21
21
  R = ::IntervalNotation::R
22
22
  Empty = ::IntervalNotation::Empty
23
23
 
24
- def int(str)
25
- IntervalSet.from_string(str)
26
- end
27
-
28
- def oo(from, to)
29
- IntervalSet.new_unsafe( [BasicIntervals::OpenOpenInterval.new(from, to)] )
24
+ def oo_basic(from, to)
25
+ BasicIntervals::OpenOpenInterval.new(from, to)
30
26
  end
31
27
 
32
- def co(from, to)
33
- IntervalSet.new_unsafe( [BasicIntervals::ClosedOpenInterval.new(from, to)] )
28
+ def co_basic(from, to)
29
+ BasicIntervals::ClosedOpenInterval.new(from, to)
34
30
  end
35
31
 
36
- def oc(from, to)
37
- IntervalSet.new_unsafe( [BasicIntervals::OpenClosedInterval.new(from, to)] )
32
+ def oc_basic(from, to)
33
+ BasicIntervals::OpenClosedInterval.new(from, to)
38
34
  end
39
35
 
40
- def cc(from, to)
36
+ def cc_basic(from, to)
41
37
  if from != to
42
- IntervalSet.new_unsafe( [BasicIntervals::ClosedClosedInterval.new(from, to)] )
38
+ BasicIntervals::ClosedClosedInterval.new(from, to)
43
39
  else
44
- IntervalSet.new_unsafe( [BasicIntervals::Point.new(from)] )
40
+ BasicIntervals::Point.new(from)
45
41
  end
46
42
  end
47
43
 
48
- def pt(value)
49
- IntervalSet.new_unsafe( [BasicIntervals::Point.new(value)] )
44
+ def pt_basic(value)
45
+ BasicIntervals::Point.new(value)
50
46
  end
51
47
 
52
- def lt(value)
53
- IntervalSet.new_unsafe( [BasicIntervals::OpenOpenInterval.new(-Float::INFINITY, value)] )
48
+ def lt_basic(value)
49
+ BasicIntervals::OpenOpenInterval.new(-Float::INFINITY, value)
54
50
  end
55
51
 
56
- def le(value)
57
- IntervalSet.new_unsafe( [BasicIntervals::OpenClosedInterval.new(-Float::INFINITY, value)] )
52
+ def le_basic(value)
53
+ BasicIntervals::OpenClosedInterval.new(-Float::INFINITY, value)
58
54
  end
59
55
 
60
- def gt(value)
61
- IntervalSet.new_unsafe( [BasicIntervals::OpenOpenInterval.new(value, Float::INFINITY)] )
56
+ def gt_basic(value)
57
+ BasicIntervals::OpenOpenInterval.new(value, Float::INFINITY)
62
58
  end
63
59
 
64
- def ge(value)
65
- IntervalSet.new_unsafe( [BasicIntervals::ClosedOpenInterval.new(value, Float::INFINITY)] )
60
+ def ge_basic(value)
61
+ BasicIntervals::ClosedOpenInterval.new(value, Float::INFINITY)
66
62
  end
67
63
 
68
- module_function :oo, :co, :oc, :cc, :pt, :lt, :le, :gt, :ge, :int
64
+ module_function :oo_basic, :co_basic, :oc_basic, :cc_basic, :pt_basic, :lt_basic, :le_basic, :gt_basic, :ge_basic
65
+
66
+ def interval(str)
67
+ IntervalSet.from_string(str)
68
+ end
69
+
70
+ def oo(from, to); IntervalSet.new_unsafe( [oo_basic(from, to)] ); end
71
+ def co(from, to); IntervalSet.new_unsafe( [co_basic(from, to)] ); end
72
+ def oc(from, to); IntervalSet.new_unsafe( [oc_basic(from, to)] ); end
73
+ def cc(from, to); IntervalSet.new_unsafe( [cc_basic(from, to)] ); end
74
+ def pt(value); IntervalSet.new_unsafe( [pt_basic(value)] ); end
75
+ def lt(value); IntervalSet.new_unsafe( [lt_basic(value)] ); end
76
+ def le(value); IntervalSet.new_unsafe( [le_basic(value)] ); end
77
+ def gt(value); IntervalSet.new_unsafe( [gt_basic(value)] ); end
78
+ def ge(value); IntervalSet.new_unsafe( [ge_basic(value)] ); end
79
+ module_function :oo, :co, :oc, :cc, :pt, :lt, :le, :gt, :ge, :interval
69
80
  end
70
81
 
71
82
  # Long syntax for interval factory methods
@@ -73,50 +84,63 @@ module IntervalNotation
73
84
  R = ::IntervalNotation::R
74
85
  Empty = ::IntervalNotation::Empty
75
86
 
76
- def interval(str)
77
- IntervalSet.from_string(str)
78
- end
79
-
80
- def open_open(from, to)
81
- IntervalSet.new_unsafe( [BasicIntervals::OpenOpenInterval.new(from, to)] )
87
+ def open_open_basic(from, to)
88
+ BasicIntervals::OpenOpenInterval.new(from, to)
82
89
  end
83
90
 
84
- def closed_open(from, to)
85
- IntervalSet.new_unsafe( [BasicIntervals::ClosedOpenInterval.new(from, to)] )
91
+ def closed_open_basic(from, to)
92
+ BasicIntervals::ClosedOpenInterval.new(from, to)
86
93
  end
87
94
 
88
- def open_closed(from, to)
89
- IntervalSet.new_unsafe( [BasicIntervals::OpenClosedInterval.new(from, to)] )
95
+ def open_closed_basic(from, to)
96
+ BasicIntervals::OpenClosedInterval.new(from, to)
90
97
  end
91
98
 
92
- def closed_closed(from, to)
99
+ def closed_closed_basic(from, to)
93
100
  if from != to
94
- IntervalSet.new_unsafe( [BasicIntervals::ClosedClosedInterval.new(from, to)] )
101
+ BasicIntervals::ClosedClosedInterval.new(from, to)
95
102
  else
96
- IntervalSet.new_unsafe( [BasicIntervals::Point.new(from)] )
103
+ BasicIntervals::Point.new(from)
97
104
  end
98
105
  end
99
106
 
100
- def point(value)
101
- IntervalSet.new_unsafe( [BasicIntervals::Point.new(value)] )
107
+ def point_basic(value)
108
+ BasicIntervals::Point.new(value)
102
109
  end
103
110
 
104
- def less_than(value)
105
- IntervalSet.new_unsafe( [BasicIntervals::OpenOpenInterval.new(-Float::INFINITY, value)] )
111
+ def less_than_basic(value)
112
+ BasicIntervals::OpenOpenInterval.new(-Float::INFINITY, value)
106
113
  end
107
114
 
108
- def less_than_or_equal_to(value)
109
- IntervalSet.new_unsafe( [BasicIntervals::OpenClosedInterval.new(-Float::INFINITY, value)] )
115
+ def less_than_or_equal_to_basic(value)
116
+ BasicIntervals::OpenClosedInterval.new(-Float::INFINITY, value)
110
117
  end
111
118
 
112
- def greater_than(value)
113
- IntervalSet.new_unsafe( [BasicIntervals::OpenOpenInterval.new(value, Float::INFINITY)] )
119
+ def greater_than_basic(value)
120
+ BasicIntervals::OpenOpenInterval.new(value, Float::INFINITY)
114
121
  end
115
122
 
116
- def greater_than_or_equal_to(value)
117
- IntervalSet.new_unsafe( [BasicIntervals::ClosedOpenInterval.new(value, Float::INFINITY)] )
123
+ def greater_than_or_equal_to_basic(value)
124
+ BasicIntervals::ClosedOpenInterval.new(value, Float::INFINITY)
118
125
  end
119
126
 
127
+ module_function :open_open_basic, :closed_open_basic, :open_closed_basic, :closed_closed_basic, :point_basic,
128
+ :less_than_basic, :less_than_or_equal_to_basic, :greater_than_basic, :greater_than_or_equal_to_basic
129
+
130
+ def interval(str)
131
+ IntervalSet.from_string(str)
132
+ end
133
+
134
+ def open_open(from, to); IntervalSet.new_unsafe([ open_open_basic(from, to) ]); end
135
+ def closed_open(from, to); IntervalSet.new_unsafe([ closed_open_basic(from, to) ]); end
136
+ def open_closed(from, to); IntervalSet.new_unsafe([ open_closed_basic(from, to) ]); end
137
+ def closed_closed(from, to); IntervalSet.new_unsafe([ closed_closed_basic(from, to) ]); end
138
+ def point(value); IntervalSet.new_unsafe([ point_basic(value) ]); end
139
+ def less_than(value); IntervalSet.new_unsafe([ less_than_basic(value) ]); end
140
+ def less_than_or_equal_to(value); IntervalSet.new_unsafe([ less_than_or_equal_to_basic(value) ]); end
141
+ def greater_than(value); IntervalSet.new_unsafe([ greater_than_basic(value) ]); end
142
+ def greater_than_or_equal_to(value); IntervalSet.new_unsafe([ greater_than_or_equal_to_basic(value) ]); end
143
+
120
144
  module_function :open_open, :closed_open, :open_closed, :closed_closed, :point,
121
145
  :less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to,
122
146
  :interval
@@ -8,6 +8,9 @@ module IntervalNotation
8
8
  def closing
9
9
  !opening
10
10
  end
11
+ def singular_point?
12
+ !interval_boundary
13
+ end
11
14
  end
12
15
 
13
16
  module ActslikeInterval
@@ -208,7 +211,7 @@ module IntervalNotation
208
211
  def eql?(other); other.class.equal?(self.class) && value == other.value; end
209
212
  def ==(other); other.is_a?(Point) && value == other.value; end
210
213
  def interval_boundaries(interval_index)
211
- BoundaryPoint.new(from, true, nil, interval_index, false)
214
+ [BoundaryPoint.new(from, true, nil, interval_index, false)]
212
215
  end
213
216
  def integer_points; value..value; end
214
217
 
@@ -74,6 +74,16 @@ module IntervalNotation
74
74
  end
75
75
  alias covered_by? contained_by?
76
76
 
77
+ # Find connected component of an interval set which covers a point.
78
+ # Return `BasicIntervals` for this connected component or nil if point is not covered.
79
+ def interval_covering_point(value)
80
+ interval = @intervals.bsearch{|interv| value <= interv.to }
81
+ if interval && interval.include_position?(value)
82
+ interval
83
+ else
84
+ nil
85
+ end
86
+ end
77
87
 
78
88
  def bsearch_last_not_meeting_condition(arr)
79
89
  found_ind = (0...arr.size).bsearch{|idx| yield(arr[idx]) } # find first not meeting condition
@@ -220,12 +230,12 @@ module IntervalNotation
220
230
 
221
231
  # Difference between an interval set and another interval set +other+. Alias: +-+
222
232
  def subtract(other)
223
- SubtractCombiner.new.combine([self, other])
233
+ SweepLine.make_interval_set([self, other], SweepLine::TraceState::Subtract.initial_state)
224
234
  end
225
235
 
226
236
  # Symmetric difference between an interval set and another interval set +other+. Alias: +^+
227
237
  def symmetric_difference(other)
228
- SymmetricDifferenceCombiner.new.combine([self, other])
238
+ SweepLine.make_interval_set([self, other], SweepLine::TraceState::SymmetricDifference.initial_state)
229
239
  end
230
240
 
231
241
  # Complement of an interval set in R. Alias: +~+
@@ -245,6 +255,16 @@ module IntervalNotation
245
255
  self
246
256
  end
247
257
 
258
+ # Obtain nonadjacent contiguous intervals (connected components) from which whole interval set consists
259
+ def connected_components
260
+ intervals.map(&:to_interval_set)
261
+ end
262
+
263
+ # Create true/false `Segmentation` corresponding to an `IntervalSet`
264
+ def make_segmentation
265
+ SweepLine.make_segmentation({self => nil}, SweepLine::TraceState::Union.initial_state(1))
266
+ end
267
+
248
268
  class << self
249
269
  # auxiliary method to check that intervals are sorted and don't overlap
250
270
  def check_valid?(intervals)
@@ -1,17 +1,17 @@
1
1
  require_relative 'interval_set'
2
2
  require_relative 'basic_intervals'
3
- require_relative 'combiners'
3
+ require_relative 'sweep_line'
4
4
 
5
5
  module IntervalNotation
6
6
  module Operations
7
7
  # Union of multiple intervals.
8
8
  def union(intervals)
9
- UnionCombiner.new(intervals.size).combine(intervals)
9
+ SweepLine.make_interval_set(intervals, SweepLine::TraceState::Union.initial_state(intervals.size))
10
10
  end
11
11
 
12
12
  # Intersection of multiple intervals
13
13
  def intersection(intervals)
14
- IntersectCombiner.new(intervals.size).combine(intervals)
14
+ SweepLine.make_interval_set(intervals, SweepLine::TraceState::Intersection.initial_state(intervals.size))
15
15
  end
16
16
 
17
17
  module_function :union, :intersection
@@ -0,0 +1,113 @@
1
+ require_relative 'sweep_line/sweep_line'
2
+
3
+ module IntervalNotation
4
+
5
+ # Segmentation is a sorted list of non-overlapping contiguous segments (singular point can be a segment too)
6
+ # covering whole R, i.e. (-∞; +∞).
7
+ # Each segment is an instance of `Segment` class, i.e. it is an interval which bear some state on it.
8
+ # State can be represented by any object. Intervals are expected to be `BasicIntervals`.
9
+ #
10
+ # Segmentations are treated equal if states of each point match. Thus adjacent segments can have the same state,
11
+ # in this case they will be glued together. Such normalization allows for comparison of segmentations.
12
+ # Note! Class provides no guarantees to store boundaries between same-state segments. They can be glued at any time.
13
+ Segmentation = Struct.new(:segments)
14
+
15
+ # Wqe have to reopen class to make a lexical scope for Segmentation::Segment
16
+ class Segmentation
17
+ # Helper class to store a state of a segment
18
+ Segment = Struct.new(:interval, :state) do
19
+ def to_s
20
+ if state.is_a?(Set) # Set#to_s don't show any content. Quite uninformative
21
+ set_elements = '{' + state.to_a.join(', ') + '}'
22
+ "<#{interval}: #{set_elements}>"
23
+ else
24
+ "<#{interval}: #{state}>"
25
+ end
26
+ end
27
+ def inspect; to_s; end
28
+ end
29
+
30
+ # Don't skip validation unless you're sure, that Segmentation is a correct one
31
+ # Don't skip normalization unless you're sure, that all adjacent intervals are glued together:
32
+ # without normalization step comparison of two segmentations will give wrong results.
33
+ def initialize(segments, skip_validation: false, skip_normalization: false, &block)
34
+ super(segments, &block)
35
+ unless skip_validation
36
+ raise 'Segmentation is not valid' unless valid?
37
+ end
38
+ join_same_state_segments! unless skip_normalization
39
+ end
40
+
41
+ # Check that segments don't overlap, cover whole R, and go one-after-another
42
+ private def valid?
43
+ return false if segments.empty?
44
+ return false unless segments.all?{|segment|
45
+ segment.is_a?(Segment)
46
+ }
47
+ return false unless segments.all?{|segment|
48
+ interval = segment.interval
49
+ interval.respond_to?(:from) && interval.respond_to?(:include_from?) && \
50
+ interval.respond_to?(:to) && interval.respond_to?(:include_to?)
51
+ }
52
+ first_interval = segments.first.interval
53
+ last_interval = segments.last.interval
54
+ return false unless first_interval.from == -Float::INFINITY && last_interval.to == Float::INFINITY
55
+ return false unless segments.each_cons(2).all?{|segment_1, segment_2|
56
+ segment_1.interval.to == segment_2.interval.from && (segment_1.interval.include_to? ^ segment_2.interval.include_from?)
57
+ }
58
+ true
59
+ end
60
+
61
+ # Join adjacent intervals with exactly the same state in order to compactify and normalize segmentation
62
+ private def join_same_state_segments!
63
+ new_segments = segments.chunk(&:state).map{|state, same_state_segments|
64
+ intervals = same_state_segments.map(&:interval) # ToDo: optimize; don't map all segments, only the first and the last ones
65
+ interval = BasicIntervals.interval_by_boundary_inclusion(
66
+ intervals.first.include_from?, intervals.first.from,
67
+ intervals.last.include_to?, intervals.last.to)
68
+ Segment.new(interval, state)
69
+ }
70
+ self.segments = new_segments
71
+ end
72
+
73
+ def to_s; "Segmentation: #{segments}"; end
74
+ def inspect; to_s; end
75
+
76
+ # Make true/false segmentation based on block result. Block takes a Segment.
77
+ # If block not specified, state is converted to boolean.
78
+ # If block specified, its result is converted to boolean.
79
+ def boolean_segmentation(&block)
80
+ if block_given?
81
+ map_state{|segment| !!block.call(segment) }
82
+ else
83
+ map_state{|segment| !!segment.state }
84
+ end
85
+ end
86
+
87
+ # Transform segmentation into interval set according with block result.
88
+ # Block takes a Segment and its result'd indicate whether to include corresponding interval into interval set
89
+ def make_interval_set(&block)
90
+ intervals = boolean_segmentation(&block).segments.select(&:state).map(&:interval)
91
+ IntervalSet.new( intervals )
92
+ end
93
+
94
+ # Method `#map_state` returns a new segmentation with the same boundaries and different states
95
+ # Block for `#map_state` takes a segment and returns new state of segment
96
+ def map_state(&block)
97
+ new_segments = segments.map{|segment| Segment.new(segment.interval, block.call(segment)) }
98
+ Segmentation.new(new_segments, skip_validation: true) # here we can skip validation but not normalization
99
+ end
100
+
101
+ # Find a segment in which specifying point falls. It always exist, so it can't return nil.
102
+ def segment_covering_point(value)
103
+ segment_index = (0...segments.size).bsearch{|segment_index| value <= segments[segment_index].interval.to }
104
+ segment = segments[segment_index]
105
+ if segment.interval.include_position?(value)
106
+ segment
107
+ else
108
+ segments[segment_index + 1]
109
+ end
110
+ end
111
+ end
112
+
113
+ end