interval_notation 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +92 -4
- data/TODO.md +1 -0
- data/lib/interval_notation.rb +72 -48
- data/lib/interval_notation/basic_intervals.rb +4 -1
- data/lib/interval_notation/interval_set.rb +22 -2
- data/lib/interval_notation/operations.rb +3 -3
- data/lib/interval_notation/segmentation.rb +113 -0
- data/lib/interval_notation/sweep_line.rb +3 -0
- data/lib/interval_notation/sweep_line/sweep_line.rb +97 -0
- data/lib/interval_notation/sweep_line/trace_state/intersection.rb +56 -0
- data/lib/interval_notation/sweep_line/trace_state/multiple_state.rb +46 -0
- data/lib/interval_notation/sweep_line/trace_state/subtract.rb +20 -0
- data/lib/interval_notation/sweep_line/trace_state/symmetric_difference.rb +20 -0
- data/lib/interval_notation/sweep_line/trace_state/tagging.rb +72 -0
- data/lib/interval_notation/sweep_line/trace_state/union.rb +57 -0
- data/lib/interval_notation/sweep_line/trace_states.rb +6 -0
- data/lib/interval_notation/version.rb +1 -1
- data/spec/interval_notation_spec.rb +42 -0
- data/spec/segmentation_spec.rb +177 -0
- data/spec/tagging_spec.rb +69 -0
- metadata +16 -3
- data/lib/interval_notation/combiners.rb +0 -166
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c1800a973a0b109fedd3e4a0bc378af0f491c93
|
4
|
+
data.tar.gz: 224ceae1be9ff756b5fb5c0dd8fdc4688edd0585
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 =
|
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
|
-
|
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
|
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)
|
data/lib/interval_notation.rb
CHANGED
@@ -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/
|
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
|
25
|
-
|
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
|
33
|
-
|
28
|
+
def co_basic(from, to)
|
29
|
+
BasicIntervals::ClosedOpenInterval.new(from, to)
|
34
30
|
end
|
35
31
|
|
36
|
-
def
|
37
|
-
|
32
|
+
def oc_basic(from, to)
|
33
|
+
BasicIntervals::OpenClosedInterval.new(from, to)
|
38
34
|
end
|
39
35
|
|
40
|
-
def
|
36
|
+
def cc_basic(from, to)
|
41
37
|
if from != to
|
42
|
-
|
38
|
+
BasicIntervals::ClosedClosedInterval.new(from, to)
|
43
39
|
else
|
44
|
-
|
40
|
+
BasicIntervals::Point.new(from)
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
48
|
-
def
|
49
|
-
|
44
|
+
def pt_basic(value)
|
45
|
+
BasicIntervals::Point.new(value)
|
50
46
|
end
|
51
47
|
|
52
|
-
def
|
53
|
-
|
48
|
+
def lt_basic(value)
|
49
|
+
BasicIntervals::OpenOpenInterval.new(-Float::INFINITY, value)
|
54
50
|
end
|
55
51
|
|
56
|
-
def
|
57
|
-
|
52
|
+
def le_basic(value)
|
53
|
+
BasicIntervals::OpenClosedInterval.new(-Float::INFINITY, value)
|
58
54
|
end
|
59
55
|
|
60
|
-
def
|
61
|
-
|
56
|
+
def gt_basic(value)
|
57
|
+
BasicIntervals::OpenOpenInterval.new(value, Float::INFINITY)
|
62
58
|
end
|
63
59
|
|
64
|
-
def
|
65
|
-
|
60
|
+
def ge_basic(value)
|
61
|
+
BasicIntervals::ClosedOpenInterval.new(value, Float::INFINITY)
|
66
62
|
end
|
67
63
|
|
68
|
-
module_function :
|
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
|
77
|
-
|
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
|
85
|
-
|
91
|
+
def closed_open_basic(from, to)
|
92
|
+
BasicIntervals::ClosedOpenInterval.new(from, to)
|
86
93
|
end
|
87
94
|
|
88
|
-
def
|
89
|
-
|
95
|
+
def open_closed_basic(from, to)
|
96
|
+
BasicIntervals::OpenClosedInterval.new(from, to)
|
90
97
|
end
|
91
98
|
|
92
|
-
def
|
99
|
+
def closed_closed_basic(from, to)
|
93
100
|
if from != to
|
94
|
-
|
101
|
+
BasicIntervals::ClosedClosedInterval.new(from, to)
|
95
102
|
else
|
96
|
-
|
103
|
+
BasicIntervals::Point.new(from)
|
97
104
|
end
|
98
105
|
end
|
99
106
|
|
100
|
-
def
|
101
|
-
|
107
|
+
def point_basic(value)
|
108
|
+
BasicIntervals::Point.new(value)
|
102
109
|
end
|
103
110
|
|
104
|
-
def
|
105
|
-
|
111
|
+
def less_than_basic(value)
|
112
|
+
BasicIntervals::OpenOpenInterval.new(-Float::INFINITY, value)
|
106
113
|
end
|
107
114
|
|
108
|
-
def
|
109
|
-
|
115
|
+
def less_than_or_equal_to_basic(value)
|
116
|
+
BasicIntervals::OpenClosedInterval.new(-Float::INFINITY, value)
|
110
117
|
end
|
111
118
|
|
112
|
-
def
|
113
|
-
|
119
|
+
def greater_than_basic(value)
|
120
|
+
BasicIntervals::OpenOpenInterval.new(value, Float::INFINITY)
|
114
121
|
end
|
115
122
|
|
116
|
-
def
|
117
|
-
|
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
|
-
|
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
|
-
|
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 '
|
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
|
-
|
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
|
-
|
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
|