rangeary 1.0.1 → 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/ChangeLog +18 -0
- data/Makefile +2 -1
- data/News +4 -0
- data/README.ja.rdoc +461 -260
- data/lib/rangeary/util/hash_inf.rb +233 -0
- data/lib/rangeary/util.rb +727 -0
- data/lib/rangeary.rb +1420 -0
- data/rangeary.gemspec +51 -0
- data/test/tee_io.rb +111 -0
- data/test/test_rangeary.rb +400 -79
- metadata +25 -19
- data/lib/rangeary/rangeary.rb +0 -1643
@@ -0,0 +1,727 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "range_extd" if !defined? RangeExtd
|
4
|
+
require_relative "util/hash_inf.rb"
|
5
|
+
|
6
|
+
class Rangeary < Array
|
7
|
+
# Utility containing {HashInf} module and some private methods for internal use for both Rangeary instance and class methods.
|
8
|
+
#
|
9
|
+
# External RangeExtd (range_extd) gem must be required.
|
10
|
+
# This module is supposed to be used in combination with {Rangeary}.
|
11
|
+
#
|
12
|
+
# Many methods have been migrated from +rangeary.rb+ so that this module
|
13
|
+
# is self-consistent, i.e, this module does not call Rangeary-specific
|
14
|
+
# methods except for {Rangeary.initialize}.
|
15
|
+
#
|
16
|
+
# All methods (but the module {HashInf}) in this module are both
|
17
|
+
# included and extended by {Rangeary}. All methods in this module
|
18
|
+
# are private, because any public methods in this modules would be
|
19
|
+
# exposed as both class and instance methods, which would be confusing to users.
|
20
|
+
#
|
21
|
+
# Some of the methods in this library are named +_core+, but they may be
|
22
|
+
# in practice exactly what the original would be rather than only the core part
|
23
|
+
# and the corresponding public method in the main file +rangeary.rb+ does
|
24
|
+
# no more than simply calls it. The reason of the structure is
|
25
|
+
# simply because this module is both included and extended (a bit tricky combination)!
|
26
|
+
module Util
|
27
|
+
|
28
|
+
# private constants: correspondance to keywords to Range methods
|
29
|
+
POSNEG2METHOD = { negative: :begin, positive: :end }
|
30
|
+
private_constant :POSNEG2METHOD
|
31
|
+
|
32
|
+
# private constants: Reverse order of the classes in guessing implicit infinities.
|
33
|
+
PRIORITIES = [FalseClass, NilClass, Numeric, RangeExtd::Infinity, Object]
|
34
|
+
private_constant :PRIORITIES
|
35
|
+
|
36
|
+
# private constants: Positive implicit infinities in the reverse order.
|
37
|
+
PRIORI_OBJ = [false, nil, Float::INFINITY, RangeExtd::Infinity::POSITIVE, Object.new] # the last one is meaningless!
|
38
|
+
# NOTE: if you change the order, make sure to ajust the numbers in _get_adjusted_infinities().
|
39
|
+
private_constant :PRIORI_OBJ
|
40
|
+
|
41
|
+
####################
|
42
|
+
private
|
43
|
+
####################
|
44
|
+
|
45
|
+
## Returns infinities literally included in the given Range
|
46
|
+
##
|
47
|
+
## @param ran [Rangeary, Range]
|
48
|
+
## @return [Hash<Object>] 2 keys of :positive and :negative. value of false if not found anything.
|
49
|
+
#def _definite_infinities_from_range(ran)
|
50
|
+
# return HashInf.construct if ran.is_none? # RangeExtd::NONE
|
51
|
+
|
52
|
+
# hs =
|
53
|
+
# HashInf.construct.map{|k, v|
|
54
|
+
# [k, ((v.respond_to?(:infinite?) && v.infinite?) ? v : nil)]
|
55
|
+
# }.to_h # This is a standard Hash (NOT HashInf-extended)
|
56
|
+
|
57
|
+
# stats = hs.map{|k, v| [k, ((false == v) ? nil : :definite)]}.to_h
|
58
|
+
#
|
59
|
+
# HashInf.construct(hs, status: stats)
|
60
|
+
#end
|
61
|
+
#private :_definite_infinities_from_range
|
62
|
+
|
63
|
+
# Get an infinity and status from a single value
|
64
|
+
#
|
65
|
+
# The returned +status+ is either of the first two {HashInf::STATUS_ORDERS}
|
66
|
+
# that is, excluding +nil+; +status+ is never +nil+ because the infinity is
|
67
|
+
# always guessable for a "valid" Range, i.e., Range with all elements mutually
|
68
|
+
# comparable, except for +RangeExtd::NONE+ .
|
69
|
+
# (The caller must judge about +RangeExtd::NONE+)
|
70
|
+
#
|
71
|
+
# The result will be fed to {HashInf#set_posinega_with_status}.
|
72
|
+
#
|
73
|
+
# This routine does not (and cannot) check the validity of the infinity,
|
74
|
+
# e.g., the positive infinity may be -Float::INFINITY .
|
75
|
+
#
|
76
|
+
# @param val [Object] nil, if it is a begin/end of a Borderless Range.
|
77
|
+
# @param posneg [Symbol] Either :negative or :positive (infinity)
|
78
|
+
# @return [Array<Object, Symbol>] 2-element Array of [infinity, :definite|:guessed]
|
79
|
+
def _get_infinity_status_single(val, posneg)
|
80
|
+
# Two cases of :definite
|
81
|
+
if val.nil?
|
82
|
+
return [nil, :definite]
|
83
|
+
elsif val.respond_to?(:infinite?) && val.infinite?
|
84
|
+
return [val, :definite]
|
85
|
+
end
|
86
|
+
|
87
|
+
# The status is now :guessed
|
88
|
+
inf =
|
89
|
+
case val
|
90
|
+
when RangeExtd::Infinity
|
91
|
+
val
|
92
|
+
when Numeric
|
93
|
+
((:negative == posneg) ? -1 : 1) * Float::INFINITY
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
[inf, :guessed]
|
98
|
+
end
|
99
|
+
private :_get_infinity_status_single
|
100
|
+
|
101
|
+
|
102
|
+
# Returns infinities taken or guessed from multiple Range-s and Rangeary-s
|
103
|
+
#
|
104
|
+
# See {#_get_infinities_from_obj} for detail.
|
105
|
+
#
|
106
|
+
# @param arran [Array<Rangeary, Range>]
|
107
|
+
# @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative.
|
108
|
+
def _get_infinities_from_multiple(*arran)
|
109
|
+
reths = HashInf.construct
|
110
|
+
|
111
|
+
flatten_no_rangeary_core(arran).each do |rang| # Range, RangeExtd, Rangeary
|
112
|
+
_validate_opts_infinities([rang], infs: reths)
|
113
|
+
reths.merge_hashinf!(_get_infinities_from_obj(rang))
|
114
|
+
end
|
115
|
+
reths
|
116
|
+
end
|
117
|
+
private :_get_infinities_from_multiple
|
118
|
+
|
119
|
+
|
120
|
+
# Core routine for {Rangeary.flatten_no_rangeary}
|
121
|
+
#
|
122
|
+
# @param arin [Array<Range, RangeExtd, Rangeary, Array>]
|
123
|
+
# @return [Array<Range, RangeExtd, Rangeary>]
|
124
|
+
def flatten_no_rangeary_core(*arin)
|
125
|
+
arret = []
|
126
|
+
arin.each do |val|
|
127
|
+
if val.respond_to?(:infinities) || val.respond_to?(:exclude_end?)
|
128
|
+
arret << val
|
129
|
+
elsif val.respond_to?(:flatten)
|
130
|
+
arret.concat send(__method__, *val)
|
131
|
+
else
|
132
|
+
raise ArgumentError, "Argument Array must consist of only Range (RangeExtd) or Array (Rangeary)"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
arret
|
136
|
+
end
|
137
|
+
private :flatten_no_rangeary_core
|
138
|
+
|
139
|
+
# Returns infinities taken or guessed from the given Range or Rangeary
|
140
|
+
#
|
141
|
+
# If the given Range has an infinity (judged with [#infinity?])
|
142
|
+
# at its either end, it is respected, and +nil+ is the same.
|
143
|
+
# Otherwise, guessed from the elements.
|
144
|
+
#
|
145
|
+
# The returned Hash extended with HashInf module consists of
|
146
|
+
# two keys of +:positive+ and +:negative+ for respective infinities.
|
147
|
+
# Also, it has the method {HashInf#definite?}, {HashInf#guessed?},
|
148
|
+
# {HashInf#status} and alike to represent the degree of confidence.
|
149
|
+
#
|
150
|
+
# @param ran [Rangeary, Range]
|
151
|
+
# @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative.
|
152
|
+
def _get_infinities_from_obj(ran)
|
153
|
+
return _get_infinities_from_range(ran) if ran.respond_to? :exclude_end?
|
154
|
+
return ran.instance_variable_get("@infinities") if ran.respond_to? :infinities
|
155
|
+
# to get the instance variable @infinities, as opposed to Rangeary#infinities
|
156
|
+
raise ArgumentError, "Neither Range/RangeExtd nor Rangeary is given."
|
157
|
+
end
|
158
|
+
private :_get_infinities_from_obj
|
159
|
+
|
160
|
+
# Returns infinities taken or guessed from elements of the given Range
|
161
|
+
#
|
162
|
+
# If the given Range has an infinity (judged with [#infinity?])
|
163
|
+
# at its either end, it is respected, and +nil+ is the same.
|
164
|
+
# Otherwise, guessed from the elements.
|
165
|
+
#
|
166
|
+
# The returned Hash extended with HashInf module consists of
|
167
|
+
# two keys of +:positive+ and +:negative+ for respective infinities.
|
168
|
+
# Also, it has the method {HashInf#definite?}, {HashInf#guessed?},
|
169
|
+
# {HashInf#status} and alike to represent the degree of confidence.
|
170
|
+
#
|
171
|
+
# @param ran [Rangeary, Range]
|
172
|
+
# @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative.
|
173
|
+
def _get_infinities_from_range(ran)
|
174
|
+
reths = HashInf.construct
|
175
|
+
return reths if ran.is_none? # RangeExtd::NONE
|
176
|
+
|
177
|
+
POSNEG2METHOD.each_pair do |posneg, metho| # n.b. method() is a Ruby built-in function.
|
178
|
+
val = ran.send(metho)
|
179
|
+
inf, stat = _get_infinity_status_single(val, posneg)
|
180
|
+
reths.set_posinega_with_status(posneg, inf, stat)
|
181
|
+
end
|
182
|
+
|
183
|
+
_get_adjusted_infinities(reths)
|
184
|
+
end
|
185
|
+
private :_get_infinities_from_range
|
186
|
+
|
187
|
+
# Returns adjusted infinities
|
188
|
+
#
|
189
|
+
# "adjusted" means:
|
190
|
+
# for example, when a Range of +("b"..RangeExtd::Infinity::POSITIVE)+ is given,
|
191
|
+
# the standard guess of infinities (with #{_get_infinity_status_single})
|
192
|
+
# are +(nil..RangeExtd::Infinity::POSITIVE)+.
|
193
|
+
# However, the two infinities are inconsistent and it is better
|
194
|
+
# to be +(RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE)+
|
195
|
+
# given that the user deliberately set +RangeExtd::Infinity::POSITIVE+.
|
196
|
+
# In such a case, the tentative negative infinity "nil" is replaced with
|
197
|
+
# +RangeExtd::Infinity::NEGATIVE+ to make everything consistent.
|
198
|
+
#
|
199
|
+
# In short, this method looks into the infinities at both ends of a Range,
|
200
|
+
# validates them, and corrects them if need be.
|
201
|
+
#
|
202
|
+
# Note that if the infinities at both ends are a user specifies they are respected
|
203
|
+
# unless both infinites have the same polarity, in which case the initialized
|
204
|
+
# default infinities {HashInf} is returned.
|
205
|
+
#
|
206
|
+
# Priorities are in the standard order (though {Util::PRIORITIES} is in the reverse order):
|
207
|
+
#
|
208
|
+
# 1. exact value
|
209
|
+
# 2. RangeExtd::Infinity
|
210
|
+
# 3. Float::INFINITY
|
211
|
+
# 4. nil
|
212
|
+
# 5. false
|
213
|
+
#
|
214
|
+
# This order basically follows the order of unlikeliness of what a user does.
|
215
|
+
# For example, +RangeExtd::Infinity+ cannot never be set unless a user deliberately sets it.
|
216
|
+
# Therefore, if a user does it, it should be respected. But if a user doesn't,
|
217
|
+
# other infinities must be used (in Ruby-2.7). Note +false+ is the initial state.
|
218
|
+
#
|
219
|
+
# Note that if an exact value is given, unless the other end is ":definite",
|
220
|
+
# nil is set.
|
221
|
+
#
|
222
|
+
# @param ininf [Hash] 2 keys of :positive and :negative.
|
223
|
+
# @return [Hash<Object>] (HashInf) 2 keys of :positive and :negative. false if not found anything.
|
224
|
+
def _get_adjusted_infinities(ininf)
|
225
|
+
reths = HashInf.construct(ininf)
|
226
|
+
|
227
|
+
# If both infinites have the same polarity, the information is reset.
|
228
|
+
return HashInf.construct() if _infinites_with_same_polarity?(*(ininf.values))
|
229
|
+
|
230
|
+
# Sets the priority Hash (not HashInf).
|
231
|
+
hspri = {}
|
232
|
+
reths.each_pair do |posneg, inf|
|
233
|
+
hspri[posneg] =
|
234
|
+
PRIORITIES.each_with_index do |klass, i|
|
235
|
+
break i if inf.is_a?(klass)
|
236
|
+
end
|
237
|
+
raise "Should not happen" if !hspri[posneg]
|
238
|
+
end
|
239
|
+
return reths if hspri[:positive] == hspri[:negative]
|
240
|
+
|
241
|
+
if hspri[:positive] < hspri[:negative]
|
242
|
+
# :negative has a higher priority
|
243
|
+
|
244
|
+
# This might happen when
|
245
|
+
# the Infinity at an end is a user-specified one (Object) and the other end
|
246
|
+
# is RangeExtd::Infinity or Float::INFINITY
|
247
|
+
return reths if reths.definite?(:positive)
|
248
|
+
|
249
|
+
inf =
|
250
|
+
case hspri[:negative]
|
251
|
+
when 2, 3
|
252
|
+
PRIORI_OBJ[hspri[:negative]] # either RangeExtd::Infinity::POSITIVE or Float::INFINITY
|
253
|
+
when 4
|
254
|
+
# user-defined infinite object is taken into account.
|
255
|
+
(reths[:positive].respond_to?(:infinite?) && reths[:positive].infinite?) ? reths[:positive] : nil
|
256
|
+
else # should be 1 only ((false..false) may be an exception, though invalid for RangeExted or Rangeary?)
|
257
|
+
nil
|
258
|
+
end
|
259
|
+
reths.set_positive_with_status(inf, :guessed)
|
260
|
+
return reths
|
261
|
+
end
|
262
|
+
|
263
|
+
return reths if reths.definite?(:negative) # Very unlikely, but it might happen (see above).
|
264
|
+
|
265
|
+
# Now, negative infinity is overwritten.
|
266
|
+
begin # "begin" just for indentation.
|
267
|
+
inf =
|
268
|
+
case hspri[:positive]
|
269
|
+
when 2, 3
|
270
|
+
-PRIORI_OBJ[hspri[:positive]] # either RangeExtd::Infinity::NEGATIVE or -Float::INFINITY
|
271
|
+
when 4
|
272
|
+
# user-defined infinite object is taken into account.
|
273
|
+
(reths[:negative].respond_to?(:infinite?) && reths[:negative].infinite?) ? reths[:negative] : nil
|
274
|
+
else
|
275
|
+
nil
|
276
|
+
end
|
277
|
+
end
|
278
|
+
reths.set_negative_with_status(inf, :guessed)
|
279
|
+
|
280
|
+
reths
|
281
|
+
end
|
282
|
+
private :_get_adjusted_infinities
|
283
|
+
|
284
|
+
# Validate @infinities from the command-line options.
|
285
|
+
#
|
286
|
+
# This basically applies +<=>+ operator and if an Exception raises,
|
287
|
+
# converts it to ArgumentError.
|
288
|
+
# Almost no Object should fail, because +<=>+ is defined in
|
289
|
+
# the Object class and it does not fail! For example, +3 <=> "a"+
|
290
|
+
# returns +nil+ and so it does not fail.
|
291
|
+
#
|
292
|
+
# @param arin [Array<Range, RangeExtd, Rangeary>]
|
293
|
+
# @param infs [Hash]
|
294
|
+
# @return [void]
|
295
|
+
# @raise [ArgumentError]
|
296
|
+
def _validate_opts_infinities(arin, infs: @infinities)
|
297
|
+
infs.each_pair do |ek, my_inf|
|
298
|
+
next if !my_inf #|| RangeExtd::Infinity.infinite?(my_inf)
|
299
|
+
arin.flatten.each do |er| # Rangeary is flattened.
|
300
|
+
next if er.is_none? # Required for Ruby 2.7+ (for updated RangeExtd::NONE for beginless Range)
|
301
|
+
[er.begin, er.end].each do |ev|
|
302
|
+
next if !ev
|
303
|
+
next if (is_num_type?(ev) && is_num_type?(my_inf))
|
304
|
+
begin
|
305
|
+
case my_inf <=> ev
|
306
|
+
when -1, 0, 1
|
307
|
+
next
|
308
|
+
else # nil or ralse
|
309
|
+
next
|
310
|
+
end
|
311
|
+
rescue
|
312
|
+
msg = "invalid parameter for :#{ek} => (#{my_inf.inspect}), incompatible with the range with Range=(#{er.inspect})."
|
313
|
+
raise ArgumentError, msg
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
private :_validate_opts_infinities
|
320
|
+
|
321
|
+
|
322
|
+
# Returns the candidate @infinities from the input Rangeary
|
323
|
+
#
|
324
|
+
# If there is any Rangeary in the input.
|
325
|
+
#
|
326
|
+
# Example return:
|
327
|
+
# { :positive => [nil, Float::INFINITY],
|
328
|
+
# :negative => [nil, -Float::INFINITY] }
|
329
|
+
#
|
330
|
+
# Note the standard @infinities is a Hash, but NOT a Hash of Array.
|
331
|
+
# This method returns the candidates.
|
332
|
+
#
|
333
|
+
# @param arin [Array<Hash<Infinity, nil>>] Inherited infinities from the input Rangeary-s
|
334
|
+
# @return [Hash<Array>] keys (:positive and :negative) Array#size may not agree between them.
|
335
|
+
def _get_cand_infinities(arin)
|
336
|
+
hsret = { positive: [], negative: [] }
|
337
|
+
arin.each do |ec|
|
338
|
+
hsret.each_key do |k|
|
339
|
+
hsret[k] << ec[k]
|
340
|
+
end
|
341
|
+
end
|
342
|
+
hsret
|
343
|
+
end
|
344
|
+
private :_get_cand_infinities
|
345
|
+
|
346
|
+
# Sort infinities obtained from inherited objects
|
347
|
+
#
|
348
|
+
# RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
|
349
|
+
#
|
350
|
+
# @param hs_inherit [Hash<Array>] :positive => Array, etc (From Rangeary-s in the main Array given)
|
351
|
+
# @return [Hash<Array>] each key (:(posi|nega)tive) contains the sorted candidate Array.
|
352
|
+
def _sort_inherited_infinities_all(hs_inherit)
|
353
|
+
hs_inherit.map do |ek, ev|
|
354
|
+
[ek, _sort_inherited_infinities_each(ev, key=ek)]
|
355
|
+
end.to_h # Ruby 2.1 or later
|
356
|
+
end
|
357
|
+
private :_sort_inherited_infinities_all
|
358
|
+
|
359
|
+
# Sort infinities obtained from inherited objects
|
360
|
+
#
|
361
|
+
# RangeExtd::Infinity is ignored. Float::INFINITY has the lowest priority.
|
362
|
+
#
|
363
|
+
# @param ar_infs [Array] of Infinities (inherited)
|
364
|
+
# @param key [Symbol] :positive or :negative
|
365
|
+
# @return [Array]
|
366
|
+
def _sort_inherited_infinities_each(ar_infs, key=:positive)
|
367
|
+
ar_infs.map {|j|
|
368
|
+
(RangeExtd::Infinity === j) ? nil : j
|
369
|
+
}.compact.sort{|a,b|
|
370
|
+
if is_num_type?(a) && RangeExtd::Infinity.infinite?(a)
|
371
|
+
-1
|
372
|
+
elsif is_num_type?(b) && RangeExtd::Infinity.infinite?(b)
|
373
|
+
1
|
374
|
+
else
|
375
|
+
begin
|
376
|
+
(key == :positive) ? (a<=>b) : (b<=>a)
|
377
|
+
rescue
|
378
|
+
0
|
379
|
+
end
|
380
|
+
end
|
381
|
+
}
|
382
|
+
end
|
383
|
+
private :_sort_inherited_infinities_each
|
384
|
+
|
385
|
+
# True if two values are both infinite and have the same polarity
|
386
|
+
#
|
387
|
+
# Both are assumed to have methods of :positive? and :negative?,
|
388
|
+
# if they have a method of +infinite?+ (as +Float::INFINITY+ does).
|
389
|
+
#
|
390
|
+
# @param v1 [Array]
|
391
|
+
# @param v2 [Array]
|
392
|
+
def _infinites_with_same_polarity?(v1, v2)
|
393
|
+
arran = [v1, v2]
|
394
|
+
(( arran.all?{ |ea| RangeExtd::Infinity.infinite?(ea) } &&
|
395
|
+
(arran.all?(&:positive?) || arran.all?(&:negative?)) ))
|
396
|
+
#(arran.all?(&:nil?))) # allowed in Ruby 2.7+
|
397
|
+
end
|
398
|
+
private :_infinites_with_same_polarity?
|
399
|
+
|
400
|
+
|
401
|
+
# Core routine for {Rangeary.sort_ranges}
|
402
|
+
#
|
403
|
+
# @param ar [<Range, RangeExtd>] Arbitrary number.
|
404
|
+
# @return [Array<Range, RangeExtd>]
|
405
|
+
def sort_ranges_core(*ar)
|
406
|
+
ar.flatten.sort{ |a,b|
|
407
|
+
err_msg_ab = "invalid parameter (#{a.inspect} or #{b.inspect})."
|
408
|
+
|
409
|
+
if a.is_none?
|
410
|
+
next (b.is_none? ? 0 : -1)
|
411
|
+
elsif b.is_none?
|
412
|
+
next 1
|
413
|
+
end
|
414
|
+
|
415
|
+
# Since Ruby-2.6, the end can be nil (Endless Range).
|
416
|
+
# Before Ruby-2.6, they used to raise Exception (ArgumentError) in Range
|
417
|
+
# and so they would not have sneaked in to RangeExtd, either.
|
418
|
+
# The following is in that sense meaningful only for Ruby 2.6 and later.
|
419
|
+
#
|
420
|
+
# Even in Ruby-2.7, this routine need no change, because nil is
|
421
|
+
# correctly handled.
|
422
|
+
ends = { :a => a, :b => b }
|
423
|
+
ends.each_pair do |k, v|
|
424
|
+
ends[k] = comparable_beginend_core(v).end
|
425
|
+
end
|
426
|
+
|
427
|
+
# ret = begs[:a] <=> begs[:b]
|
428
|
+
ret = a.begin <=> b.begin # Without filtering with comparable_beginend()
|
429
|
+
case ret
|
430
|
+
when -1, 1
|
431
|
+
ret
|
432
|
+
when 0
|
433
|
+
a_exc_begin = (a.exclude_begin? rescue false)
|
434
|
+
b_exc_begin = (b.exclude_begin? rescue false)
|
435
|
+
if (a_exc_begin ^ b_exc_begin)
|
436
|
+
if a_exc_begin # but not b
|
437
|
+
1
|
438
|
+
else
|
439
|
+
-1
|
440
|
+
end
|
441
|
+
else # <= (a.exclude_begin? == b.exclude_begin?)
|
442
|
+
ret = ends[:a] <=> ends[:b]
|
443
|
+
case ret
|
444
|
+
when -1, 1
|
445
|
+
ret
|
446
|
+
when 0
|
447
|
+
if (a.exclude_end? && b.exclude_end?)
|
448
|
+
0
|
449
|
+
elsif a.exclude_end? # but not b
|
450
|
+
-1
|
451
|
+
else # <= b.exclude_end? but not a
|
452
|
+
1
|
453
|
+
end # if (a.exclude_end? && b.exclude_end?)
|
454
|
+
when nil
|
455
|
+
# This should not happen for Range, let alone RangeExtd.
|
456
|
+
# But could be the case for a user-defined class.
|
457
|
+
if ends[:a].nil? && ends[:b].nil?
|
458
|
+
0
|
459
|
+
elsif ends[:a].nil?
|
460
|
+
-1
|
461
|
+
elsif ends[:b].nil?
|
462
|
+
1
|
463
|
+
else
|
464
|
+
raise(TypeError, err_msg_ab)
|
465
|
+
end
|
466
|
+
else # case ret # ends[:a] <=> ends[:b]
|
467
|
+
raise(TypeError, err_msg_ab)
|
468
|
+
end # case ret # ends[:a] <=> ends[:b]
|
469
|
+
end # if (a.exclude_begin? ^ b.exclude_begin?)
|
470
|
+
when nil # case ret #(a.begin <=> b.begin)
|
471
|
+
if a.begin.nil? && b.begin.nil?
|
472
|
+
0
|
473
|
+
elsif a.begin.nil?
|
474
|
+
-1
|
475
|
+
elsif b.begin.nil?
|
476
|
+
1
|
477
|
+
else
|
478
|
+
raise(TypeError, err_msg_ab)
|
479
|
+
end
|
480
|
+
else # case ret #(a.begin <=> b.begin)
|
481
|
+
raise(TypeError, err_msg_ab)
|
482
|
+
end # case ret #(a.begin <=> b.begin)
|
483
|
+
} # ar.sort{ |a,b|
|
484
|
+
end # def sort_ranges_core(ar)
|
485
|
+
private :sort_ranges_core
|
486
|
+
|
487
|
+
|
488
|
+
# Core routine of {Rangeary.comparable_beginend}
|
489
|
+
#
|
490
|
+
# @overload self.comparable_beginend(ran, infinities: nil)
|
491
|
+
# Range or RangeExtd
|
492
|
+
# @param ran [Range, RangeExtd]
|
493
|
+
# @param infinities [Hash<Boolean>] two-elements of :positive and :negative
|
494
|
+
# @overload self.comparable_beginend(ran, vend, infinities: nil)
|
495
|
+
# Begin and End values for Range
|
496
|
+
# @param ran [Object]
|
497
|
+
# @param vend [Object]
|
498
|
+
# @param infinities [Hash<Boolean>] two-elements of :positive and :negative
|
499
|
+
#
|
500
|
+
# @return [RangeExtd] exclude statuses for begin and end follow the input.
|
501
|
+
# @raise [RangeError] if ran is not Range#valid?
|
502
|
+
def comparable_beginend_core(ran, vend=Object, infinities: nil)
|
503
|
+
if ran.respond_to? :is_none?
|
504
|
+
if ran.is_none? # Range should have the method.
|
505
|
+
return RangeExtd::NONE
|
506
|
+
end
|
507
|
+
_ = RangeExtd(ran) # may raise RangeError
|
508
|
+
else
|
509
|
+
ran = RangeExtd(ran, vend) # may raise RangeError
|
510
|
+
end
|
511
|
+
|
512
|
+
ar_inf = [infinities[:negative], infinities[:positive]] if infinities
|
513
|
+
|
514
|
+
is_numeric = [:begin, :end].any?{|method| ran.send(method).respond_to? :to_int}
|
515
|
+
arret = [:begin, :end].map.with_index{ |method, i|
|
516
|
+
obj = ran.send(method)
|
517
|
+
if obj.nil?
|
518
|
+
if infinities && ar_inf[i] # infinity of nil or false is ignored.
|
519
|
+
ar_inf[i]
|
520
|
+
else
|
521
|
+
# plus-minus is reversed only for "begin", like RangeExtd::Infinity::NEGATIVE
|
522
|
+
val = (is_numeric ? Float::INFINITY : RangeExtd::Infinity::POSITIVE)
|
523
|
+
(i == 0) ? -val : val
|
524
|
+
end
|
525
|
+
else
|
526
|
+
obj
|
527
|
+
end
|
528
|
+
}
|
529
|
+
RangeExtd(arret[0], arret[1], (ran.respond_to?(:exclude_begin?) ? ran.exclude_begin? : false), ran.exclude_end?)
|
530
|
+
end
|
531
|
+
private :comparable_beginend_core
|
532
|
+
|
533
|
+
# Internal version of Conjunction, where +infinities+ can be given.
|
534
|
+
#
|
535
|
+
# @param r1 [Rangeary, RangeExtd, Range]
|
536
|
+
# @param r2 [Rangeary, RangeExtd, Range]
|
537
|
+
# @param infs [Hash] ({HashInf}) if supplied, +infinities+ are NOT guessed from the main arguments. Note that there is an instance method called +infinities+()
|
538
|
+
# @return [Rangeary]
|
539
|
+
def conjunction_core(r1, r2, infs: nil)
|
540
|
+
|
541
|
+
r1 = Rangeary.new(r1) if ! defined? r1.first_element
|
542
|
+
r2 = Rangeary.new(r2) if ! defined? r2.first_element
|
543
|
+
|
544
|
+
hsinf = (infs || _get_infinities_from_multiple(r1, r2))
|
545
|
+
return Rangeary.new(RangeExtd::NONE, **hsinf) if r1.null? || r2.null?
|
546
|
+
#return Rangeary.new(r1, **hsinf) if r2.all?
|
547
|
+
#return Rangeary.new(r2, **hsinf) if r1.all?
|
548
|
+
return(infs ? Rangeary.new(r1, infs) : Rangeary.new(r1, **hsinf)) if r2.all?
|
549
|
+
return(infs ? Rangeary.new(r2, infs) : Rangeary.new(r2, **hsinf)) if r1.all?
|
550
|
+
|
551
|
+
# Initialisation
|
552
|
+
a1 = r1.to_a
|
553
|
+
a2 = r2.to_a
|
554
|
+
rc = Rangeary.new( RangeExtd::NONE, **hsinf ) # hsinf is essential to define @infinities
|
555
|
+
|
556
|
+
if a1.empty? || a2.empty?
|
557
|
+
return rc
|
558
|
+
end
|
559
|
+
|
560
|
+
### Algorithm
|
561
|
+
# Conditions: Both a1 and a2 are sorted in order of #begin().
|
562
|
+
# a1 is a reference array of RangeExtd
|
563
|
+
# Then, Sigma(i=0..(a2.size-1)) a2[i]*a1[j=0..-1] => Returned Rangeary
|
564
|
+
#
|
565
|
+
# In reality, I put some tricks to avoid unnecessary calculation
|
566
|
+
# for the sake of the processing speed. But essentially
|
567
|
+
# it is a very simple algorithm.
|
568
|
+
#
|
569
|
+
last_a1index = 0
|
570
|
+
a2.each do |ea2|
|
571
|
+
a1.each_with_index do |ea1, ind|
|
572
|
+
# A bit of trick just to avoid unnecessary process
|
573
|
+
next if ind < last_a1index # skip
|
574
|
+
|
575
|
+
# For Ruby-2.6 Endless Range, comparable_end() is employed.
|
576
|
+
# Note: this comparison ignores @infinities even if set,
|
577
|
+
# because @infinities may not be defined in the arguments!
|
578
|
+
# Anyway, nothing should be larger than the upper limit (as tested below),
|
579
|
+
# and so this should be fine.
|
580
|
+
#
|
581
|
+
# Now, considering Ruby-2.7 Beginless Range, too.
|
582
|
+
ran1 = comparable_beginend_core(ea1)
|
583
|
+
ran2 = comparable_beginend_core(ea2)
|
584
|
+
break if ran2.end < ran1.begin # Completely out of range
|
585
|
+
if ran1.end < ran2.begin # Completely out of range
|
586
|
+
last_a1index = ind if last_a1index < ind
|
587
|
+
next
|
588
|
+
end
|
589
|
+
|
590
|
+
# Core - Perform conjunction.
|
591
|
+
pq1 = conjunctionRangeExtd(ea1, ea2) # => Rangeary.conjunctionRangeExtd()
|
592
|
+
if ! pq1.empty?
|
593
|
+
rc += (infs ? Rangeary.new(pq1, infs) : Rangeary.new(pq1))
|
594
|
+
last_a1index = ind
|
595
|
+
end
|
596
|
+
end # a1.each_with_index do |ea1, ind|
|
597
|
+
end # a2.each do |ea2|
|
598
|
+
|
599
|
+
rc
|
600
|
+
end # def self.conjunction_core(r1, r2)
|
601
|
+
private :conjunction_core
|
602
|
+
|
603
|
+
|
604
|
+
#== Logical conjunction of two RangeExtd
|
605
|
+
#
|
606
|
+
# To assure this logical conjunction meaningful,
|
607
|
+
# the objects that consist of RangeExtd objects have to be
|
608
|
+
# monotonic (increase), namely, for any potential element,
|
609
|
+
# x_n and x_m, within the given range,
|
610
|
+
# (x_n <=> x_m) == 1 if (n > m),
|
611
|
+
# have to be true. In other words, the derivative must be always
|
612
|
+
# non-negative.
|
613
|
+
#
|
614
|
+
# For example, (?a..?d) and (?x..?z) satisfies this condition.
|
615
|
+
# However, ('d'..'gg') does not, as follows.
|
616
|
+
# rd = RangeExtd('d'..'gg')
|
617
|
+
# rf = RangeExtd('f'..'h')
|
618
|
+
# Rangeary.conjunctionRangeExtd(rd, rf) # => ('f'..'gg')
|
619
|
+
#
|
620
|
+
# @note If you give a built-in Range object(s) for the arguments,
|
621
|
+
# make sure they are valid, that is, Range#valid? returns true.
|
622
|
+
#
|
623
|
+
#=== Algorithm
|
624
|
+
#
|
625
|
+
#[st means status. - true if excl; Estimate (Init(in|ex),Fini(in|ex))]
|
626
|
+
#[b4 <= af] (sort!)
|
627
|
+
#
|
628
|
+
#(1) Init = af[0], Init.st=af[0].st
|
629
|
+
# If (b4[0]==af[0]), then Init.st = ( b4[0].ex || af[0].ex)
|
630
|
+
#(2) Fini = [b4[-1], af[-1]].min, which belongs to (b4|af)
|
631
|
+
# If (b4[-1]==af[-1]), then Fini.st = (b4[-1].ex || af[-1].ex), otherwise nil for now.
|
632
|
+
#(3) if (Init > Fini) => none.
|
633
|
+
# *---* => ....
|
634
|
+
# *--* ....
|
635
|
+
#(4) if (Init == FiniMax), then Fini.st=b4[-1].st
|
636
|
+
# (4-1) if (Init.in&&Fini.in), => Single-Number-Range(InitCand(in))
|
637
|
+
# *--I => ...I
|
638
|
+
# I--* I...
|
639
|
+
# (4-2) else, => none
|
640
|
+
#(5) if (Init < FiniMax)
|
641
|
+
# (5-1) if Fini belongs to b4, Fini.st=b4[-1].st
|
642
|
+
# *---* => .*--*
|
643
|
+
# *---*
|
644
|
+
# (5-2) if Fini belongs to af, Fini.st=af[-1].st
|
645
|
+
# *---* => .*-*
|
646
|
+
# *-*
|
647
|
+
# (5-3) if Fini belongs to both, Fini.st is defined already.
|
648
|
+
# *---* => .*--*
|
649
|
+
# *--*
|
650
|
+
#
|
651
|
+
# @param r1 [RangeExtd] Can be Range
|
652
|
+
# @param r2 [RangeExtd] Can be Range
|
653
|
+
# @return [RangeExtd]
|
654
|
+
#
|
655
|
+
def conjunctionRangeExtd(r1, r2)
|
656
|
+
|
657
|
+
[r1, r2].each do |er|
|
658
|
+
return er if er.is_none?
|
659
|
+
end
|
660
|
+
|
661
|
+
r = *( sort_ranges_core([RangeExtd(r1), RangeExtd(r2)]) ) # => Rangeary.sort_ranges
|
662
|
+
|
663
|
+
## Note: the end product will be (cBeg(:stBeg), cEnd(:stEnd))
|
664
|
+
# where :stBeg and :stEnd mean exclude_(begin|end)?
|
665
|
+
|
666
|
+
# Set the candidate begin value.
|
667
|
+
cBeg = r[1].begin
|
668
|
+
if r[0].begin == r[1].begin
|
669
|
+
stBeg = (r[1].exclude_begin? || r[0].exclude_begin?)
|
670
|
+
else
|
671
|
+
stBeg = r[1].exclude_begin?
|
672
|
+
end
|
673
|
+
|
674
|
+
# Set the candidate end value. (Rangeary.comparable_end() for Ruby-2.6 Endless Range)
|
675
|
+
# Note: this comparison ignores @infinities even if set,
|
676
|
+
# because @infinities may not be defined in the arguments!
|
677
|
+
# Anyway, nothing should be larger than the upper limit
|
678
|
+
# and so this should be fine.
|
679
|
+
if comparable_beginend_core(r[0]).end == comparable_beginend_core(r[1]).end
|
680
|
+
cEndOrig = r[1].end
|
681
|
+
cEnd = comparable_beginend_core(r[1]).end
|
682
|
+
stEnd = (r[0].exclude_end? || r[1].exclude_end?)
|
683
|
+
else
|
684
|
+
a = [[comparable_beginend_core(r[0]).end, 0, r[0].end], [comparable_beginend_core(r[1]).end, 1, r[1].end]].min
|
685
|
+
cEnd = a[0]
|
686
|
+
cEndIndex = a[1] # r[cEndIndex] == RangeExtd obj that gives the end of the resultant range.
|
687
|
+
cEndOrig = a[2]
|
688
|
+
stEnd = nil
|
689
|
+
end
|
690
|
+
|
691
|
+
case cBeg <=> cEnd
|
692
|
+
when 1 # cBeg > cEnd
|
693
|
+
RangeExtd::NONE
|
694
|
+
|
695
|
+
when 0 # cBeg == cEnd
|
696
|
+
stEnd = r[0].exclude_end?
|
697
|
+
if (!stBeg) && (!stEnd)
|
698
|
+
RangeExtd(cBeg..cBeg) # Point range
|
699
|
+
else
|
700
|
+
RangeExtd::NONE
|
701
|
+
end
|
702
|
+
|
703
|
+
when -1 # cBeg < cEnd
|
704
|
+
# Now, the range must be (cBeg, cEnd). May need adjustment of the exclude status.
|
705
|
+
if stEnd.nil?
|
706
|
+
stEnd = r[cEndIndex].exclude_end?
|
707
|
+
# else
|
708
|
+
# # Already defined.
|
709
|
+
end # if stEnd.nil?
|
710
|
+
|
711
|
+
RangeExtd(cBeg, cEndOrig, :exclude_begin => stBeg, :exclude_end => stEnd)
|
712
|
+
else
|
713
|
+
raise
|
714
|
+
end # case cBeg <=> cEnd
|
715
|
+
|
716
|
+
end # def self.conjunctionRangeExtd(r1, r2)
|
717
|
+
private :conjunctionRangeExtd
|
718
|
+
|
719
|
+
# True if object is a type of Numeric and comparable
|
720
|
+
def is_num_type?(obj)
|
721
|
+
Numeric === obj && obj.respond_to?(:between?) && obj.class.method_defined?(:<)
|
722
|
+
end
|
723
|
+
private :is_num_type?
|
724
|
+
|
725
|
+
end # module Util
|
726
|
+
end # class Rangeary < Array
|
727
|
+
|