rangeary 0.1.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.
@@ -0,0 +1,1176 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ## Load required files.
4
+ # require "range_extd/range_extd"
5
+ err1st = nil
6
+ req_files = %w(lib/range_extd/range_extd)
7
+ req_files.each do |req_file|
8
+ while ! req_file.empty?
9
+ begin
10
+ require req_file
11
+ rescue LoadError => errLoad
12
+ err1st = errLoad if err1st.nil?
13
+ if %r@/@ =~ req_file
14
+ if req_file.sub!(%r@[^/]*/@, '').nil? # Will search for the next directory down.
15
+ raise
16
+ end
17
+ else
18
+ req_file = ''
19
+ break
20
+ end
21
+ else
22
+ break
23
+ end
24
+ end
25
+ if req_file.empty?
26
+ raise err1st
27
+ end
28
+ end # req_files.each do |req_file|
29
+
30
+
31
+
32
+ def Rangeary(*rest)
33
+ Rangeary.new(*rest)
34
+ end
35
+
36
+ # =Class Rangeary < Array
37
+ #
38
+ # Authors:: Masa Sakano
39
+ # License:: MIT
40
+ #
41
+ # ==Summary
42
+ #
43
+ # Class to express the multiple ranges.
44
+ #
45
+ # The library package <tt>range_extd</tt> is required.
46
+ # {https://rubygems.org/gems/range_extd}
47
+ #
48
+ class Rangeary < Array
49
+ undef_method :*, :+, :length, :reverse
50
+
51
+ # Hash with the keys of :negative and :positive
52
+ attr_reader :infinities
53
+
54
+ #
55
+ # Arbitrary (positive) number of arguments can be given.
56
+ # (r1, [r2, ...])
57
+ #
58
+ # It is possible to supply the user-defined infinity objects for both or either
59
+ # positive and negative infinity. If one (or more) of the arguments is
60
+ # a {Rangeary} object, their infinity values are inherited, unless explicitly
61
+ # specified in the optional arguments.
62
+ #
63
+ # @param inarall [Object] An arbitrary number of either {Rangeary}, {RangeExtd} or {Range} objects (or its subclasses).
64
+ # @option **opts [Object] :positive Object for positive infinity. In default {Float::INFINITY} for Numeric Real or else {RangeExtd::Infinity::POSITIVE}.
65
+ # @option **opts [Object] :negative Object for negative infinity. In default -{Float::INFINITY} for Numeric Real or else {RangeExtd::Infinity::NEGATIVE}.
66
+ def initialize(*inarall, **opts)
67
+
68
+ if inarall.size < 1
69
+ raise ArgumentError, "wrong number of arguments (#{inarall.size} for 1 or more)."
70
+ end
71
+
72
+ hsInheritedObj = {:negative =>nil, :positive =>nil}
73
+ hsInheritedAry = {:negative => [], :positive => []}
74
+ hsInheritedClass = {:negative => [], :positive => []}
75
+ inarall = inarall.map{|i|
76
+ if defined?(i.first_element) && defined?(i.infinities)
77
+ begin
78
+ [:negative, :positive].each do |nega_posi|
79
+ hsInheritedAry[nega_posi].push( i.infinities[nega_posi])
80
+ hsInheritedClass[nega_posi].push(i.infinities[nega_posi].class)
81
+ end
82
+ rescue
83
+ warn "warning: Rangeary#infinities looks wrong in the input (#{i})."
84
+ end
85
+ i.to_a
86
+ else
87
+ i
88
+ end
89
+ }.flatten.map{|j|
90
+ if (defined? j.exclude_begin?)
91
+ j
92
+ else
93
+ begin
94
+ RangeExtd.new(j)
95
+ rescue ArgumentError # Just to change the error message.
96
+ raise ArgumentError, "invalid parameter for RangeExtd, hence for Rangeary (#{j.inspect})."
97
+ end
98
+ end
99
+ }
100
+
101
+ # _merge_overlaps
102
+ begin
103
+ arRange = _merge_overlaps( convert2range(inarall) )
104
+ rescue => err
105
+ # Trap just to change the type of the exception.
106
+ raise ArgumentError, err.message, err.backtrace
107
+ end
108
+
109
+ if arRange.empty?
110
+ raise ArgumentError, 'no significant argument given for Rangeary.'
111
+ end
112
+
113
+ ## Setting @infinities[:negative, :positive]
114
+
115
+ # Check inherited objects if there is any, namely if the argument includes any RangeAry object.
116
+ # Priority: Float > Others > RangeExtd::Infinity
117
+ if hsInheritedAry[:negative].size > 0
118
+ [:negative, :positive].each do |es|
119
+ iFloat = hsInheritedClass[es].find_index(Float)
120
+ if iFloat.nil?
121
+ iElse = hsInheritedClass[es].find_index{|i| (i != RangeExtd::Infinity) && (i != Float)}
122
+ if iElse.nil?
123
+ iRangeInf = hsInheritedClass[es].find_index(RangeExtd::Infinity)
124
+ if iRangeInf.nil?
125
+ raise "Rangeary#infinities is not set in the input." # Should not happen, as Rangeary#infinities must be set always.
126
+ else
127
+ hsInheritedObj[es] = hsInheritedAry[es][iRangeInf]
128
+ end
129
+ else
130
+ hsInheritedObj[es] = hsInheritedAry[es][iElse]
131
+ end
132
+ else
133
+ hsInheritedObj[es] = hsInheritedAry[es][iFloat]
134
+ end # if iFloat.nil?
135
+ end # [:negative, :positive].each do |es|
136
+ end # if hsInheritedAry.size > 0
137
+
138
+ hsFlag = { :found => {:negative => false, :positive => false} }
139
+ hsCand = { :negative => arRange[0].begin, :positive => arRange[-1].end }
140
+ infDef = { :negative => RangeExtd::Infinity::NEGATIVE, :positive => RangeExtd::Infinity::POSITIVE }
141
+ @infinities={ :negative => nil, :positive => nil }
142
+ [:negative, :positive].each do |es|
143
+ if (infDef[es] == hsCand[es]) # Can be Float or whatever.
144
+ @infinities[es] = hsCand[es] # highest priority
145
+ hsFlag[:found][:negative] = true
146
+ else
147
+ strtmp = ""
148
+ [opts[es], hsInheritedObj[es]].each do |opts_or_inherited|
149
+ @infinities[es] ||= opts_or_inherited # uses ots[:****tive] or hsInheritedObj[:****tive] if not set.
150
+ # Now, checking the compatibility of the infinity value specified (or inherited) with the given range.
151
+ if (! opts_or_inherited.nil?) && (opts_or_inherited == @infinities[es]) && (! arRange[0].is_none?)
152
+ begin
153
+ _ = 0 * (opts_or_inherited <=> hsCand[es])
154
+ rescue TypeError
155
+ raise ArgumentError, "invalid #{strtmp}parameter for :#{es} => (#{opts_or_inherited.inspect}), incompatible with the range with begin=(#{hsCand[es].inspect})."
156
+ end
157
+ end
158
+ strtmp = "inherited "
159
+ end # [opts[es], hsInheritedObj[es]].each do |opts_or_inherited|
160
+ end # if (infDef[es] == hsCand[es]) # else
161
+ end # [:negative, :positive].each do |es|
162
+
163
+ if ! (@infinities[:negative] && @infinities[:positive])
164
+ # Either or both @infinities[:negative, :positive] is not set, yet.
165
+ # Need to set it now. The possibilities are,
166
+ # (1) arRange[0].null? && no opts/inheritance given.
167
+ # (2) one of them is given by either arRange or opts or inheritance, but not the other.
168
+ # (3) neither of them is given by arRange nor opts nor inheritance.
169
+ if arRange[0].null?
170
+ [:negative, :positive].each do |es|
171
+ @infinities[es] ||= infDef[es]
172
+ end
173
+ else
174
+ # There must be a non-infinity object - we will find it out.
175
+ if hsFlag[:found][:negative]
176
+ obj2refer = arRange[0].end
177
+ else
178
+ obj2refer = arRange[-1].begin
179
+ end
180
+
181
+ # Now, if Numeric === obj2refer, Float::INFINITY is the default.
182
+ begin
183
+ _dummy = (1.0 < obj2refer)
184
+ rescue ArgumentError
185
+ # Not Numeric, hence the current infDef is used as it is.
186
+ else
187
+ # Numeric
188
+ infDef = { :negative => -Float::INFINITY, :positive => Float::INFINITY }
189
+ end
190
+ [:negative, :positive].each do |es|
191
+ @infinities[es] ||= infDef[es] # uses default infinity if not set.
192
+ end
193
+ end # if arRange[0].null?
194
+ end # if ! (@infinities[:negative] && @infinities[:positive])
195
+
196
+ super(arRange)
197
+ self.freeze
198
+ end # def initialize(*inarall, **opts)
199
+
200
+
201
+ alias :triple_equals_orig :===
202
+
203
+ # True if the inObj is in the range.
204
+ #
205
+ # This method works on the basis of each element, that is,
206
+ # if for any of the {RangeExtd} in the {Rangeary}, {RangeExtd#===}
207
+ # returns true, this will return true. That means, if the argument is
208
+ # a {Rangeary} (or {Range}) object, this always returns false.
209
+ # Note {#include?} and {#member?} work the same as in the standard {Array},
210
+ # whereas {#include_element?} and {#member_element?} are the alias of
211
+ # this method.
212
+ #
213
+ # See {#cover?}. The difference between this method and {#cover?} is
214
+ # the same as that in {Range}.
215
+ # @return [Boolean]
216
+ def ===(inObj)
217
+ to_a.each do |ea|
218
+ if ea === inObj
219
+ return true
220
+ end
221
+ end
222
+ return false
223
+ end
224
+
225
+ alias :include_element? :===
226
+ alias :member_element? :===
227
+
228
+
229
+ # @return [Object] The {RangeExtd#begin} of the first {RangeExtd}.
230
+ def begin()
231
+ if to_a.size > 0
232
+ to_a[0].begin
233
+ else
234
+ nil # Should not happen!
235
+ end
236
+ end
237
+ alias :begin_element :begin
238
+
239
+
240
+ # If inObj is within the ranges, it will return true.
241
+ #
242
+ # See {#===}.
243
+ # The difference between this method and {#===} is
244
+ # the same as that in {Range}.
245
+ def cover?(inObj)
246
+ to_a.each do |ea|
247
+ if ea.cover? inObj
248
+ return true
249
+ elsif (ea.end <=> inObj) == 1
250
+ return false # No point of carrying on searching.
251
+ end
252
+ end
253
+ return false
254
+ end # def cover?(inObj)
255
+
256
+
257
+ # Iterator for each element in the ranges.
258
+ # @return [self]
259
+ def each_element
260
+ each do |er|
261
+ er.each do |ee|
262
+ yield ee
263
+ end
264
+ end
265
+ self
266
+ end
267
+
268
+
269
+ # If the range defined in this object is empty (as in {Range#empty?}), returns true.
270
+ #
271
+ def empty_element?
272
+ each do |er|
273
+ if ! er.empty?
274
+ return false
275
+ end
276
+ end
277
+ return true
278
+ end
279
+
280
+
281
+ # @return [Object] The {RangeExtd#end} of the last {RangeExtd}.
282
+ def end()
283
+ if to_a.size > 0
284
+ to_a[-1].end
285
+ else
286
+ nil
287
+ end
288
+ end
289
+ alias :end_element :end
290
+
291
+
292
+ # Returns the first n elements of the entire range, the same as {Range#first}.
293
+ #
294
+ # If the argument is not given, this simply calls {Range#begin} for the first
295
+ # {RangeExtd}.
296
+ #
297
+ # If not, and if the elements in the ranges are not discrete, like Float,
298
+ # an exception is raised (see {Range#first}).
299
+ # Note this works on the element basis, being different from {Rangeary#first},
300
+ # which works on the array basis.
301
+ #
302
+ # @param n [Integer] (Optional) positive.
303
+ # @return [Object] equivalent to {#begin} if no argument is given.
304
+ # @return [Array] Array of the first n elements in the range.
305
+ # @raise [TypeError] if the ranges has no iteration defined, such as, starting from Float.
306
+ def first_element(n=nil)
307
+ if n.nil?
308
+ self.begin()
309
+ elsif n < 0
310
+ raise ArgumentError, "the argument #{n} has to be positive."
311
+ else
312
+ arRet = []
313
+ m = n
314
+ to_a.each do |eachr|
315
+ ar = eachr.first(m)
316
+ arRet += ar
317
+ if arRet.size >= n
318
+ break
319
+ else
320
+ m -= ar.size
321
+ end
322
+ end
323
+
324
+ arRet
325
+ end # if n.nil?
326
+ end # def first_element(n=nil)
327
+
328
+
329
+ # Return an array of objects that consist of the ranges.
330
+ #
331
+ # @return [Array]
332
+ # @raise [TypeError] if any of the ranges has no iteration defined, such as, starting from Float.
333
+ # @example
334
+ # Rangeary(2...4, 5..6, 8..9).to_a # => [2...4, 5..6, 8..9]
335
+ # Rangeary(2...4, 5..6, 8..9).flatten # => [2, 3, 5, 6, 8, 9]
336
+ # Rangeary(2...4, 5..6, 8..9).flatten.reduce(:+) # => 33
337
+ def flatten
338
+ to_a.reduce([]){|a,b| a+=b.to_a}
339
+ end # def flatten
340
+
341
+
342
+ # @return [String]
343
+ def inspect
344
+ "<Rangeary:#{to_a.inspect}>"
345
+ end
346
+
347
+
348
+ # Can iterate?
349
+ def iteratable?
350
+ begin
351
+ each do |i|
352
+ i.each{break}
353
+ end
354
+ rescue TypeError
355
+ return false
356
+ end
357
+ true
358
+ end # def iteratable?
359
+
360
+
361
+ # Returns the last n elements of the entire range, the same as {Range#last}.
362
+ #
363
+ # If the argument is not given, this simply calls {Range#end} for the last
364
+ # {RangeExtd}.
365
+ #
366
+ # If not, and if the elements in the ranges are not discrete, like Float,
367
+ # an exception is raised (see {Range#last}).
368
+ # Note this works on the element basis, being different from {Rangeary#last},
369
+ # which works on the array basis.
370
+ #
371
+ # @param n [Integer] (Optional) positive.
372
+ # @return [Object] equivalent to {#end} if no argument is given.
373
+ # @return [Array] Array of the last n elements in the range.
374
+ # @raise [TypeError] if any of the ranges has no iteration defined, such as, starting from Float.
375
+ def last_element(n=nil)
376
+ if n.nil?
377
+ self.end()
378
+ elsif n < 0
379
+ raise ArgumentError, "the argument #{n} has to be positive."
380
+ else
381
+ arRet = []
382
+ m = n
383
+ to_a.reverse.each do |eachr|
384
+ ar = eachr.last(m)
385
+ arRet = ar + arRet
386
+ if arRet.size >= n
387
+ break
388
+ else
389
+ m -= ar.size
390
+ end
391
+ end
392
+
393
+ arRet
394
+ end # if n.nil?
395
+ end # def last_element(n=nil)
396
+
397
+
398
+ # Practically equivalent to {#empty_element?}.
399
+ def null_element?
400
+ each do |er|
401
+ if ! er.null?
402
+ return false
403
+ end
404
+ end
405
+ return true
406
+ end
407
+ alias :null? :null_element?
408
+
409
+
410
+ # Return the sum of {RangeExtd#size} of all the ranges in the object.
411
+ #
412
+ # @return [Integer] 0 if {RangeExtd::NONE}
413
+ # @return [Float] Float::INFINITY if one of ranges is open-ended.
414
+ # @return [nil] if any of the range is non-Numeric and not open-ended to the infinity.
415
+ def size_element()
416
+ begin
417
+ to_a.map(&:size).reduce(:+)
418
+ rescue TypeError
419
+ nil
420
+ end
421
+ end
422
+
423
+
424
+ # ======================= Operators =======================
425
+
426
+ # Disjunction of a Rangeary (or RangeExtd or Range) and another
427
+ #
428
+ # @param r1 [Rangeary, RangeExtd, Range]
429
+ # @param r2 [Rangeary, RangeExtd, Range]
430
+ # @return [Rangeary]
431
+ def self.disjunction(r1, r2)
432
+ self.new(r1, r2)
433
+ end
434
+
435
+ # Add (Disjunction) a Rangeary (or RangeExtd or Range)
436
+ #
437
+ # @param inr [Rangeary, RangeExtd, Range]
438
+ # @return [Rangeary]
439
+ def disjunction(inr)
440
+ self.class.new(self, inr)
441
+ end
442
+
443
+ alias :+ :disjunction
444
+ alias :| :disjunction # "|" (plus with Object#eql?) in general, but in this case it is identical.
445
+
446
+ # Exclusive Disjunction (XOR) with a Rangeary (or RangeExtd or Range)
447
+ #
448
+ # @param r1 [Rangeary, RangeExtd, Range]
449
+ # @param r2 [Rangeary, RangeExtd, Range]
450
+ # @return [Rangeary]
451
+ def self.exclusive_disjunction(r1, r2)
452
+ Rangeary.new(r1).exclusive_disjunction(r2)
453
+ end
454
+
455
+ # Exclusive Disjunction (XOR) with a Rangeary (or RangeExtd or Range)
456
+ #
457
+ # @param inr [Rangeary, RangeExtd, Range]
458
+ # @return [Rangeary]
459
+ def exclusive_disjunction(inr)
460
+ (disjunction(inr)).conjunction(conjunction(inr).negation)
461
+ end
462
+
463
+ alias :^ :exclusive_disjunction
464
+ alias :xor :exclusive_disjunction
465
+
466
+ # Subtraction.
467
+ #
468
+ # @param r [Rangeary, RangeExtd, Range]
469
+ # @return [Rangeary]
470
+ def subtraction(r)
471
+ conjunction( Rangeary.new(r).negation )
472
+ end
473
+
474
+ alias :- :subtraction
475
+
476
+
477
+ # Conjunction.
478
+ #
479
+ # @param r [Rangeary, RangeExtd, Range]
480
+ # @return [Rangeary]
481
+ def conjunction(r)
482
+ self.class.conjunction(self, r)
483
+ end
484
+
485
+ alias :& :conjunction
486
+ alias :* :conjunction
487
+
488
+
489
+ # Negation.
490
+ #
491
+ # @param r [Rangeary, RangeExtd, Range]
492
+ # @return [Rangeary]
493
+ def self.negation(r)
494
+ self.new(r).negation
495
+ end
496
+
497
+
498
+ # Negation.
499
+ #
500
+ # @return [Rangeary]
501
+ def negation()
502
+ if to_a.empty?
503
+ raise "ERROR: No range is defined." # This should not happen.
504
+ end
505
+
506
+ arran = []
507
+ prevend = nil
508
+ prevst = nil
509
+ to_a.each do |eachr|
510
+
511
+ # ALL -> NONE
512
+ return Rangeary.new(RangeExtd::NONE, :positive => @infinities[:positive], :negative => @infinities[:negative]) if RangeExtd::ALL == eachr
513
+
514
+ # null(NONE) -> ALL
515
+ if eachr.null?
516
+ begin
517
+ _beg = 1.0 * eachr.begin
518
+ return (-Float::INFINITY..Float::INFINITY)
519
+ rescue TypeError # XXXX can't be coerced into Float
520
+ return Rangeary.new(@infinities[:negative]..@infinities[:positive])
521
+ end
522
+ end
523
+
524
+ ea_b = eachr.begin
525
+ if (RangeExtd::Infinity::NEGATIVE == ea_b) # Including -Float::INFINITY and other general negative infinities (nb., [#==] is not commutative!).
526
+ # The existing first range starts from the negative infinity, so skip this one.
527
+ else
528
+ if prevend.nil?
529
+ # The returned first range starts from the negative infinity.
530
+ arran.push( RangeExtd(@infinities[:negative]..eachr.begin, :exclude_end => (! eachr.exclude_begin?)) )
531
+ else
532
+ arran.push( RangeExtd(prevend, eachr.begin, :exclude_begin => (! prevst), :exclude_end => (! eachr.exclude_begin?)) )
533
+ end
534
+ end # if (eachr.begin == -Float::INFINITY) || (RangeExtd::NONE == eachr.begin)
535
+ prevend = eachr.end
536
+ prevst = eachr.exclude_end?
537
+ end # to_a.each do |eachr|
538
+
539
+ if (RangeExtd::Infinity::POSITIVE == prevend) # Including Float::INFINITY and other general positive infinities (nb., [#==] is not commutative!).
540
+ ## Do nothing
541
+ else
542
+ arran.push( RangeExtd.new(prevend..@infinities[:positive], :exclude_begin => (! prevst)) )
543
+ end
544
+
545
+ Rangeary.new(arran, :positive => @infinities[:positive], :negative => @infinities[:negative])
546
+ end # def negation()
547
+
548
+ alias :~@ :negation
549
+
550
+
551
+ ####################
552
+ # Public class methods
553
+ ####################
554
+
555
+ # Conjunction.
556
+ #
557
+ # @param r1 [Rangeary, RangeExtd, Range]
558
+ # @param r2 [Rangeary, RangeExtd, Range]
559
+ # @return [Rangeary]
560
+ def self.conjunction(r1, r2)
561
+
562
+ r1 = Rangeary.new(r1) if ! defined? r1.first_element
563
+ r2 = Rangeary.new(r2) if ! defined? r2.first_element
564
+
565
+ # Getting inherited options (if Rangeary is given) for the later use.
566
+ hsInherited = {}
567
+ [:negative, :positive].each do |ei|
568
+ hsInherited[ei] = [r1.infinities[ei], r2.infinities[ei]].map{|j|
569
+ (RangeExtd::Infinity === j) ? nil : j
570
+ }.compact.sort{|a,b|
571
+ if a.class == Float
572
+ -1
573
+ elsif b.class == Float
574
+ 1
575
+ else
576
+ 0
577
+ end
578
+ }[0]
579
+ end
580
+
581
+ # Initialisation
582
+ a1 = r1.to_a
583
+ a2 = r2.to_a
584
+ rc = Rangeary.new( RangeExtd::NONE, hsInherited )
585
+
586
+ if a1.empty? || a2.empty?
587
+ return rc
588
+ end
589
+
590
+ ### Algorithm
591
+ # Conditions: Both a1 and a2 are sorted in order of #begin().
592
+ # a1 is a reference array of RangeExtd
593
+ # Then, Sigma(i=0..(a2.size-1)) a2[i]*a1[j=0..-1] => Returned Rangeary
594
+ #
595
+ # In reality, I put some tricks to avoid unnecessary calculation
596
+ # for the sake of the processing speed. But essentially
597
+ # it is a very simple algorithm.
598
+ #
599
+ last_a1index = 0
600
+ a2.each do |ea2|
601
+ a1.each_with_index do |ea1, ind|
602
+ # A bit of trick just to avoid unnecessary process
603
+ next if ind < last_a1index # skip
604
+ break if ea2.end < ea1.begin # Completely out of range
605
+ if ea1.end < ea2.begin # Completely out of range
606
+ last_a1index = ind if last_a1index < ind
607
+ next
608
+ end
609
+
610
+ # Core - Perform conjunction.
611
+ pq1 = conjunctionRangeExtd(ea1, ea2) # => Rangeary.conjunctionRangeExtd()
612
+ if ! pq1.empty?
613
+ rc += Rangeary.new(pq1)
614
+ last_a1index = ind
615
+ end
616
+ end # a1.each_with_index do |ea1, ind|
617
+ end # a2.each do |ea2|
618
+
619
+ rc
620
+ end # def self.conjunction(r1, r2)
621
+
622
+
623
+ # Returns the array sorted, based on (1) the begin objects and their boundary state,
624
+ # (2) then the end objects and their boundary state.
625
+ # {RangeExtd::NONE} comes first, if included in the input array.
626
+ #
627
+ # @param ar [<Range, RangeExtd>] Arbitrary number.
628
+ # @return [Array<Range, RangeExtd>]
629
+ def self.sort_ranges(*ar)
630
+ ar.flatten.sort{ |a,b|
631
+ err_msg_ab = "invalid parameter (#{a.inspect} or #{b.inspect})."
632
+ ret = a.begin <=> b.begin
633
+ case ret
634
+ when -1, 1
635
+ ret
636
+ when 0
637
+ a_exc_begin = (a.exclude_begin? rescue false)
638
+ b_exc_begin = (b.exclude_begin? rescue false)
639
+ if (a_exc_begin ^ b_exc_begin)
640
+ if a_exc_begin # but not b
641
+ 1
642
+ else
643
+ -1
644
+ end
645
+ else # <= (a.exclude_begin? == b.exclude_begin?)
646
+ ret = a.end <=> b.end
647
+ case ret
648
+ when -1, 1
649
+ ret
650
+ when 0
651
+ if (a.exclude_end? && b.exclude_end?)
652
+ 0
653
+ elsif a.exclude_end? # but not b
654
+ -1
655
+ else # <= b.exclude_end? but not a
656
+ 1
657
+ end # if (a.exclude_end? && b.exclude_end?)
658
+ when nil
659
+ # This should not happen for Range, let alone RangeExtd.
660
+ # But could be the case for a user-defined class.
661
+ if a.end.nil? && b.end.nil?
662
+ 0
663
+ elsif a.end.nil?
664
+ -1
665
+ elsif b.end.nil?
666
+ 1
667
+ else
668
+ raise(TypeError, err_msg_ab)
669
+ end
670
+ else # case ret # a.end <=> b.end
671
+ raise(TypeError, err_msg_ab)
672
+ end # case ret # a.end <=> b.end
673
+ end # if (a.exclude_begin? ^ b.exclude_begin?)
674
+ when nil # case ret #(a.begin <=> b.begin)
675
+ if a.begin.nil? && b.begin.nil?
676
+ 0
677
+ elsif a.begin.nil?
678
+ -1
679
+ elsif b.begin.nil?
680
+ 1
681
+ else
682
+ raise(TypeError, err_msg_ab)
683
+ end
684
+ else # case ret #(a.begin <=> b.begin)
685
+ raise(TypeError, err_msg_ab)
686
+ end # case ret #(a.begin <=> b.begin)
687
+ } # ar.sort{ |a,b|
688
+ end # def self.sort_ranges(ar)
689
+
690
+
691
+
692
+ ####################
693
+ private
694
+ ####################
695
+
696
+ # Called from {Rangeary#initialize}
697
+ def convert2range(inarall)
698
+ inarall.flatten.map{|i|
699
+ if defined? i.first_element
700
+ i.to_a
701
+ else
702
+ i
703
+ end
704
+ }.flatten.map{|j|
705
+ if (defined? j.exclude_begin?)
706
+ j
707
+ else
708
+ RangeExtd(j)
709
+ end
710
+ }
711
+ end
712
+
713
+
714
+ # Called from {Rangeary#initialize}.
715
+ # Process the array of RangeExtd and return the new one, in which
716
+ # overlapped ranges are merged accordingly.
717
+ #
718
+ # If there is no non-"empty" range, one of them will be left.
719
+ # As a priority, an empty range with a definite class is left,
720
+ # but if there is none, RangeExtd::NONE will be left.
721
+ #
722
+ # @param inAr [Array<RangeExtd,Range>]
723
+ # @return [Array]
724
+ def _merge_overlaps(inAr)
725
+ #def self.compact(inAr)
726
+
727
+ ### Cases
728
+ #[st means status.]
729
+ #(0) (prev[0]) and (prev[0].status) unchanged.
730
+ #(1) if (now[-1]< prev[-1]), do nothing. [Totally inclusive]
731
+ # I---* => I---*
732
+ # *--*
733
+ #(2) if (now[-1]== prev[-1])
734
+ # (2-1) AND if (now[-1].st? || prev[-1].st?), prev[-1].st=T [Nearly inclusive]
735
+ # I--O => I--I
736
+ # I--I
737
+ # (2-2) ELSE do nothing. [Totally inclusive]
738
+ #(3) ELSE [namely, if (now[-1] > prev[-1])]
739
+ # (3-1) if (now[0] > prev[-1]), append. [Totally exclusive]
740
+ # *--* => *--* *--*
741
+ # *--*
742
+ # (3-2) if (now[0] == prev[-1])
743
+ # (3-2-1) (!now[0].st? && !prev[-1].st?), append. [Totally exclusive]
744
+ # *--O => *--O--*
745
+ # O--*
746
+ # (3-2-2) ELSE [namely, now[1].st? || prev[-1].st?], connect. (prev[0],now[-1])
747
+ # *--O => *-----*
748
+ # I--*
749
+ # *--I => *-----*
750
+ # O--*
751
+ # *--I => *-----*
752
+ # I--*
753
+ # (3-3) ELSE [namely, if (now[0] < prev[-1])], connect. (prev[0],now[-1])
754
+ # *--* => *---*
755
+ # *--*
756
+ #
757
+
758
+ # inRanges = sort_arrange( inAr )
759
+ inRanges = self.class.sort_ranges( inAr ).map{|i| (RangeExtd === i) ? i : RangeExtd(i) } # => Rangeary.sort_ranges(ar)
760
+
761
+ if inRanges.size < 1
762
+ return inRanges
763
+ end
764
+
765
+ newRanges = [inRanges[0]]
766
+
767
+ inRanges[1..-1].each do |eachr|
768
+ prev = newRanges[-1]
769
+ case eachr.end <=> prev.end
770
+ when -1 # aka, eachr.end < prev.end
771
+ # Do nothing [Totally inclusive]
772
+ when 0
773
+ if (!eachr.exclude_end?) && prev.exclude_end?
774
+ # Change the status (:exclude_end => false) for prev
775
+ newRanges[-1] = RangeExtd.new(prev.begin, prev.end, :exclude_begin => prev.exclude_begin?, :exclude_end => false)
776
+ else
777
+ # Do nothing [Totally inclusive]
778
+ end
779
+ when 1 # aka, eachr.end > prev.end
780
+ case eachr.begin <=> prev.end
781
+ when -1 # Connect by combining
782
+ newRanges[-1] = RangeExtd.new(prev.begin, eachr.end, :exclude_begin => prev.exclude_begin?, :exclude_end => eachr.exclude_end?)
783
+ when 0
784
+ if (eachr.exclude_begin?) && (prev.exclude_end?)
785
+ newRanges.push(eachr) # [Totally exclude]
786
+ else # Connect by combining
787
+ newRanges[-1] = RangeExtd.new(prev.begin, eachr.end, :exclude_begin => prev.exclude_begin?, :exclude_end => eachr.exclude_end?)
788
+ end
789
+ when 1
790
+ newRanges.push(eachr) # [Totally exclude]
791
+ when nil
792
+ newRanges.push(eachr) # must be RangeExtd::NONE (or user-defined equivalent)
793
+ else
794
+ raise
795
+ end # case eachr.begin <=> prev.end
796
+ when nil # aka, eachr.end > prev.end
797
+ newRanges.push(eachr) # must be RangeExtd::NONE (or user-defined equivalent)
798
+ else
799
+ raise
800
+ end # case eachr.end <=> prev.end
801
+
802
+ end # inRanges[1..-1].each do |eachr|
803
+
804
+
805
+ ## Sort out empty Ranges in the array.
806
+ # If there is at least one non-empty range, delete all empty ranges.
807
+ # If not, leave one of them, preferably not RangeExtd::NONE,
808
+ # unless there is no choice.
809
+ hsFlag = {
810
+ :empty? => true,
811
+ :klass => nil,
812
+ :found? => false,
813
+ }
814
+
815
+ # Search for non-empty range.
816
+ newRanges.each do |er|
817
+ if er.empty?
818
+ if hsFlag[:klass].nil?
819
+ obj = er.begin()
820
+ if obj.nil?
821
+ ## Do nothing
822
+ else
823
+ hsFlag[:klass] = obj.class
824
+ end
825
+ end
826
+ else
827
+ hsFlag[:empty?] = false
828
+ break
829
+ end
830
+ end
831
+
832
+ # It is all empty, hence delete all but one.
833
+ if hsFlag[:empty?]
834
+ hsFlag[:klass] = NilClass if hsFlag[:klass].nil?
835
+ newRanges.delete_if do |er|
836
+ if hsFlag[:found?]
837
+ true
838
+ elsif er.begin().class == hsFlag[:klass]
839
+ hsFlag[:found?] = true
840
+ false
841
+ else
842
+ true
843
+ end
844
+ end
845
+ else
846
+ newRanges.delete_if{ |er| er.empty? }
847
+ end
848
+
849
+ newRanges
850
+ end # def _merge_overlaps(inAr)
851
+
852
+ private :_merge_overlaps
853
+
854
+
855
+ ####################
856
+ # private_class_method
857
+ ####################
858
+
859
+ #== Logical conjunction of two RangeExtd
860
+ #
861
+ # To assure this logical conjunction meaningful,
862
+ # the objects that consist of RangeExtd objects have to be
863
+ # monotonic (increase), namely, for any potential element,
864
+ # x_n and x_m, within the given range,
865
+ # (x_n <=> x_m) == 1 if (n > m),
866
+ # have to be true. In other words, the derivative must be always
867
+ # non-negative.
868
+ #
869
+ # For example, (?a..?d) and (?x..?z) satisfies this condition.
870
+ # However, ('d'..'gg') does not, as follows.
871
+ # rd = RangeExtd('d'..'gg')
872
+ # rf = RangeExtd('f'..'h')
873
+ # Rangeary.conjunctionRangeExtd(rd, rf) # => ('f'..'gg')
874
+ #
875
+ # @note If you give a built-in Range object(s) for the arguments,
876
+ # make sure they are valid, that is, Range#valid? returns true.
877
+ #
878
+ #=== Algorithm
879
+ #
880
+ #[st means status. - true if excl; Estimate (Init(in|ex),Fini(in|ex))]
881
+ #[b4 <= af] (sort!)
882
+ #
883
+ #(1) Init = af[0], Init.st=af[0].st
884
+ # If (b4[0]==af[0]), then Init.st = ( b4[0].ex || af[0].ex)
885
+ #(2) Fini = [b4[-1], af[-1]].min, which belongs to (b4|af)
886
+ # If (b4[-1]==af[-1]), then Fini.st = (b4[-1].ex || af[-1].ex), otherwise nil for now.
887
+ #(3) if (Init > Fini) => none.
888
+ # *---* => ....
889
+ # *--* ....
890
+ #(4) if (Init == FiniMax), then Fini.st=b4[-1].st
891
+ # (4-1) if (Init.in&&Fini.in), => Single-Number-Range(InitCand(in))
892
+ # *--I => ...I
893
+ # I--* I...
894
+ # (4-2) else, => none
895
+ #(5) if (Init < FiniMax)
896
+ # (5-1) if Fini belongs to b4, Fini.st=b4[-1].st
897
+ # *---* => .*--*
898
+ # *---*
899
+ # (5-2) if Fini belongs to af, Fini.st=af[-1].st
900
+ # *---* => .*-*
901
+ # *-*
902
+ # (5-3) if Fini belongs to both, Fini.st is defined already.
903
+ # *---* => .*--*
904
+ # *--*
905
+ #
906
+ # @param r1 [RangeExtd] Can be Range
907
+ # @param r2 [RangeExtd] Can be Range
908
+ # @return [RangeExtd]
909
+ #
910
+ def self.conjunctionRangeExtd(r1, r2)
911
+
912
+ [r1, r2].each do |er|
913
+ return er if er.is_none?
914
+ end
915
+
916
+ r = *( sort_ranges([RangeExtd(r1), RangeExtd(r2)]) ) # => Rangeary.sort_ranges
917
+ # r = *( sort_arrange([RangeExtd(r1), RangeExtd(r2)]) )
918
+
919
+ ## Note: the end product will be (cBeg(:stBeg), cEnd(:stEnd))
920
+ # where :stBeg and :stEnd mean exclude_(begin|end)?
921
+
922
+ # Set the candidate begin value.
923
+ cBeg = r[1].begin
924
+ if r[0].begin == r[1].begin
925
+ stBeg = (r[1].exclude_begin? || r[0].exclude_begin?)
926
+ else
927
+ stBeg = r[1].exclude_begin?
928
+ end
929
+
930
+ # Set the candidate end value.
931
+ if r[0].end == r[1].end
932
+ cEnd = r[1].end
933
+ stEnd = (r[0].exclude_end? || r[1].exclude_end?)
934
+ else
935
+ a = [[r[0].end, 0], [r[1].end, 1]].min
936
+ cEnd = a[0]
937
+ cEndIndex = a[1] # r[cEndIndex] == RangeExtd obj that gives the end of the resultant range.
938
+ stEnd = nil
939
+ end
940
+
941
+ case cBeg <=> cEnd
942
+ when 1 # cBeg > cEnd
943
+ RangeExtd::NONE
944
+
945
+ when 0 # cBeg == cEnd
946
+ stEnd = r[0].exclude_end?
947
+ if (!stBeg) && (!stEnd)
948
+ RangeExtd(cBeg..cBeg) # Point range
949
+ else
950
+ RangeExtd::NONE
951
+ end
952
+
953
+ when -1 # cBeg < cEnd
954
+ # Now, the range must be (cBeg, cEnd). May need adjustment of the exclude status.
955
+ if stEnd.nil?
956
+ stEnd = r[cEndIndex].exclude_end?
957
+ # else
958
+ # # Already defined.
959
+ end # if stEnd.nil?
960
+
961
+ RangeExtd(cBeg, cEnd, :exclude_begin => stBeg, :exclude_end => stEnd)
962
+ else
963
+ raise
964
+ end # case cBeg <=> cEnd
965
+
966
+ end # def self.conjunctionRangeExtd(r1, r2)
967
+
968
+ private_class_method :conjunctionRangeExtd
969
+
970
+
971
+ # Logical conjunction of two Rangeary
972
+ #
973
+ #===Algorithm
974
+ #
975
+ # r1 == [p1, p2, p3, ...] # Array of RangeExtd
976
+ # r2 == [q1, q2, q3, ...]
977
+ # rc = [] # Initial value of Conjunction r1*r2
978
+ # 1. q1 = r2.shift
979
+ # 2. r1.delete_if(pi.end<q1.begin) -> (r1==[P1,P2(>q1),P3,...])
980
+ # 3. make conjunction between RangeExtd P1*q1 => pq1
981
+ # 4. rc+=Rangeary.new(pq1)
982
+ # 5a. if q1.end<P1.end, will do P1<->q2 next, hence return to 1 (will do r2.shift).
983
+ # *--* => *--* + nil
984
+ # *--* *--* .... *--*
985
+ # *---* => .O--* + -*
986
+ # *---* *--* ..... *--* ...-* ----
987
+ # 5b. if q1.end==P1.end, r1.shift, return to 1 (will do r2.shift).
988
+ # *--* *-- => .... *-- + ---* ---
989
+ # *---* *--* ..... *--* .---* ----
990
+ # 5c. if q1.end>P1.end, will do P2<->q1, hence r1.shift, return to 2.
991
+ # *---* *-- => ..... *--
992
+ # *---- .O---
993
+ #
994
+ # @return [Rangeary]
995
+ def self.conjunction_orig(r1, r2)
996
+
997
+ r1 = Rangeary.new(r1) if ! defined? r1.first_element
998
+ r2 = Rangeary.new(r2) if ! defined? r2.first_element
999
+
1000
+ # Getting inherited options (if Rangeary is given) for the later use.
1001
+ hsInherited = {}
1002
+ [:negative, :positive].each do |ei|
1003
+ hsInherited[ei] = [r1.infinities[ei], r2.infinities[ei]].map{|j|
1004
+ (RangeExtd::Infinity === j) ? nil : j
1005
+ }.compact.sort{|a,b|
1006
+ if a.class == Float
1007
+ -1
1008
+ elsif b.class == Float
1009
+ 1
1010
+ else
1011
+ 0
1012
+ end
1013
+ }[0]
1014
+ end
1015
+
1016
+ # Initialisation
1017
+ a1 = r1.to_a
1018
+ a2 = r2.to_a
1019
+ rc = Rangeary.new( RangeExtd::NONE, hsInherited )
1020
+
1021
+ if a1.empty? || a2.empty?
1022
+ return rc
1023
+ end
1024
+
1025
+ # Main loop
1026
+ hsFlag = {:a2shift => true}
1027
+ while true
1028
+ if hsFlag[:a2shift]
1029
+ if a2.size > 0
1030
+ q1 = a2.shift
1031
+ else
1032
+ break
1033
+ end
1034
+ end
1035
+ index_valid = a1.find_index{|i| i.begin >= q1.begin }
1036
+ if (! index_valid.nil?) && index_valid > 1
1037
+ a1.shift(index_valid)
1038
+ end
1039
+
1040
+ if a1.size < 1
1041
+ break
1042
+ end
1043
+ p1 = a1[0]
1044
+
1045
+ # Core - Perform conjunction.
1046
+ pq1 = Rangeary.conjunctionRangeExtd(p1, q1)
1047
+ rc += Rangeary.new(pq1)
1048
+
1049
+ case q1.end <=> p1.end
1050
+ when -1 # q1.end < p1.end
1051
+ hsFlag[:a2shift] = true
1052
+ when 0
1053
+ if a1.size > 0
1054
+ a1.shift
1055
+ else
1056
+ break
1057
+ end
1058
+ hsFlag[:a2shift] = true
1059
+ when 1 # q1.end > p1.end
1060
+ if a1.size > 0
1061
+ a1.shift
1062
+ else
1063
+ break
1064
+ end
1065
+ hsFlag[:a2shift] = false
1066
+ else
1067
+ raise
1068
+ end # case q1.end <=> p1.end
1069
+ end # while (a1.size > 0) || (a2.size > 0)
1070
+
1071
+ rc
1072
+ end
1073
+ private_class_method :conjunction_orig
1074
+
1075
+
1076
+ # Subtract a Range from an Array of Range objects.
1077
+ def self.subtractRange(arRng, r2sub)
1078
+
1079
+ ### Cases
1080
+ #[st means status.]
1081
+ #(1) if (now[0] < prev[0])
1082
+ # (1-1) if (now[-1] < prev[0]), do nothing. [Totally exclusive]
1083
+ # *--* => *--*
1084
+ # *--*
1085
+ # (1-2) if (now[-1] == prev[0])
1086
+ # (1-2-1) AND if (now[-1].in?)&&(prev[-1].in?), Change prev[0].ex?=T [Nearly exclusive]
1087
+ # I--* => O--*
1088
+ # *--I
1089
+ # (1-2-1) ELSE, do nothing. [Totally inclusive]
1090
+ # O--* => *--*
1091
+ # *--I
1092
+ # *--* => *--*
1093
+ # *--o
1094
+ # (1-3) if (now[-1] > prev[0])
1095
+ # (1-3-1) if (now[-1] < prev[-1]), => (now[-1],Prev[-1]).
1096
+ # *---* => ..*-*
1097
+ # *--*
1098
+ # (1-3-2) if (now[-1] == prev[-1]), => (now[-1],Prev[-1]).
1099
+ # (1-3-2-1) if (now[-1].ex?)&&(prev[-1].in?), (Prev[-1],Prev[-1])(Single_number)
1100
+ # *---I => ....I
1101
+ # *----O
1102
+ # (1-3-2-2) ELSE, none.
1103
+ # *---O => .....
1104
+ # *----*
1105
+ # *---* => .....
1106
+ # *----I
1107
+ # (1-3-3) if (now[-1] > prev[-1]), none
1108
+ # *---* => .....
1109
+ # *-----*
1110
+ #(2) if (now[0] == prev[0])
1111
+ # (2A-1) if (now[0].ex?)&&(prev[0].in?), => (Prev[0],Prev[0])(Single_number) + Any?
1112
+ # I--- => I???
1113
+ # O---
1114
+ # (2A-2) ELSE, none for the first part.
1115
+ # O--- => .???
1116
+ # *---
1117
+ # *--- => .???
1118
+ # I---
1119
+ # (2B) Follow (1) to get the New Array.
1120
+ #(3) if (now[0] > prev[0])
1121
+ # (3-1) if (now[-1] < prev[-1]), 2 Rangeary (Prev[0],now[0]),(now[-1],prev[-1])
1122
+ # *-----* => *-*.*-*
1123
+ # *-*
1124
+ # (3-2) if (now[-1] == prev[-1])
1125
+ # (3-2-1) if (now[-1].ex?)&&(prev[-1].in?), => (Prev[0],now[0]),(prev[-1],prev[-1])
1126
+ # *----I => *-*...I
1127
+ # *--O
1128
+ # (3-2-2) ELSE, => (Prev[0],now[0])
1129
+ # *----I => *-*....
1130
+ # *--I
1131
+ # *----O => *-*....
1132
+ # *--*
1133
+ # (3-3) if (now[-1] > prev[-1])
1134
+ # (3-3-1) if (now[0] < prev[-1]), => (Prev[0],now[0]),(prev[-1],prev[-1])
1135
+ # *---* => *-*....
1136
+ # *---*
1137
+ # (3-3-2) if (now[0] == prev[-1])
1138
+ # (3-3-2-1) if (now[0].in?)&&(prev[0].in?), => (Prev[0],prev[-1].ex)
1139
+ # *--I => *--O
1140
+ # I--*
1141
+ # (3-3-2-2) ELSE, unchanged.
1142
+ # *--* => *--*
1143
+ # O--*
1144
+ # *--O => *--O
1145
+ # *--*
1146
+ # (3-3-3) if (now[0] > prev[-1]), unchanged.
1147
+ # *--* => *--*
1148
+ # *--*
1149
+ #
1150
+
1151
+ sort_ranges(arRng).map{ |er| # => Rangeary.sort_ranges
1152
+ # sort_arrange(arRng).map{ |er|
1153
+ if er.end < r2sub.begin
1154
+ er
1155
+ elsif er.begin < r2sub.begin && er.end < r2sub.end
1156
+ (er.begin .. (r2sub.begin-1))
1157
+ elsif er.begin < r2sub.begin && r2sub.end < er.end
1158
+ [er.begin .. (r2sub.begin-1),
1159
+ (r2sub.end+1) .. er.end ]
1160
+ elsif r2sub.begin <= er.begin && er.end <= r2sub.end
1161
+ nil
1162
+ elsif r2sub.begin <= er.begin && er.begin <= r2sub.end && r2sub.end < er.end
1163
+ (r2sub.end+1) .. er.end
1164
+ elsif r2sub.end < er.begin
1165
+ er
1166
+ else
1167
+ raise "This should not happen."
1168
+ end
1169
+ }.flatten.compact
1170
+ end # def self.subtractRange(arRng, r2sub)
1171
+ private_class_method :subtractRange
1172
+
1173
+
1174
+ end
1175
+
1176
+