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,233 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class Rangeary < Array
|
4
|
+
module Util
|
5
|
+
|
6
|
+
# Module to extend a Hash class instance to hold infinities information
|
7
|
+
#
|
8
|
+
# {Rangeary} needs to know what its own infinities are.
|
9
|
+
# This class holds the information.
|
10
|
+
#
|
11
|
+
# It should have only 2 keys of +:negative+ and +:positive+ though it is
|
12
|
+
# not constrained at the class level.
|
13
|
+
#
|
14
|
+
# Method {HashInf#status} returns a Hash containing status
|
15
|
+
# information about the main values (negative and positive infinities).
|
16
|
+
# Some related utility methods are also defined.
|
17
|
+
#
|
18
|
+
# The default infinity values (for negative and positive) are +false+
|
19
|
+
# (NOT +nil+ because nil can mean the border of a beginless/endless Range),
|
20
|
+
# whereas the default (uninitialized) status for it is +nil+.
|
21
|
+
#
|
22
|
+
module HashInf
|
23
|
+
# Order Array of the {#status} parameters (smaller means a higher priority).
|
24
|
+
# If +:definite+, it means it exists in a Range/Rangeary or is specified in an argument.
|
25
|
+
STATUS_ORDERS = [:definite, :guessed, nil]
|
26
|
+
|
27
|
+
# Reverse Hash of {STATUS_ORDERS} to give the priority number for {#status}.
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# STATUS_PRIORITIES[:definite] # => 0
|
31
|
+
# STATUS_PRIORITIES[nil] # => 2
|
32
|
+
STATUS_PRIORITIES = STATUS_ORDERS.each_with_index.to_a.reverse.to_h
|
33
|
+
|
34
|
+
# Method to overwrite Hash @status . See {#status}.
|
35
|
+
attr_writer :status
|
36
|
+
|
37
|
+
# Altenative constructor
|
38
|
+
#
|
39
|
+
# Usually you can construct an instance like:
|
40
|
+
# myhs = { negative: false, positive: false }
|
41
|
+
# myhs.extend HashInf
|
42
|
+
# myhs.status = { negative: negative, positive: positive }
|
43
|
+
#
|
44
|
+
# This constructor is a shortcut.
|
45
|
+
#
|
46
|
+
# Note that the returned object is a different object from the argument,
|
47
|
+
# and so you are free to destructively modify it.
|
48
|
+
#
|
49
|
+
# If the argument is a {HashInf}, the contents are copied.
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# HashInf.construct() # => <HashInf: {negative: false, positive: false}, status: {negative: nil, positive: nil}>
|
53
|
+
# HashInf.construct({negative: false, positive: false}, status: {negative: :guessed})
|
54
|
+
#
|
55
|
+
# @param infinities [Hash, NilClass] if given, make sure the Hash has 2 keys of :negative and :positive with relevant infinity values, e.g., +Float::INFINITY+ or nil or else.
|
56
|
+
# @param status [Hash] Optional. 1 or 2 keys of :negative and :positive with values of {HashInf::STATUS_ORDERS}.
|
57
|
+
# @return [Hash] extended with {HashInf}
|
58
|
+
def self.construct(infinities=nil, status: {})
|
59
|
+
rethsinf = {negative: false, positive: false}
|
60
|
+
rethsinf.merge!(infinities) if infinities
|
61
|
+
rethsinf.extend HashInf
|
62
|
+
rethsinf.status = {negative: nil, positive: nil}.merge(status)
|
63
|
+
rethsinf.status.merge!(infinities.status) if infinities.respond_to? :status
|
64
|
+
rethsinf.status.merge!(status)
|
65
|
+
rethsinf
|
66
|
+
end
|
67
|
+
|
68
|
+
# Reader and initializer of @status
|
69
|
+
#
|
70
|
+
# status is a two-key (+:negative+ and +:positive+) Hash with values of one of
|
71
|
+
#
|
72
|
+
# 1. nil (initial condition)
|
73
|
+
# 2. +:guessed+ (if it is guessed, such as +Float::INFINITY+ because Range contains an Integer.)
|
74
|
+
# 3. +:definite+ (if it is user-specified or exists in a Range, such as +nil+ in +(5..nil)+.
|
75
|
+
def status
|
76
|
+
return @status if @status
|
77
|
+
@status = {negative: nil, positive: nil}
|
78
|
+
end
|
79
|
+
|
80
|
+
# merges another {HashInf} (or Hash) like +Hash#merge+, taking into account the status, and returns a new {HashInf}
|
81
|
+
#
|
82
|
+
# If other is a (non-HashInf) Hash, it should have no more than 2 keys of :positive and :negative.
|
83
|
+
#
|
84
|
+
# If the statuses {HashInf#status} are the same between self and other, the more extreme
|
85
|
+
# value is adopted. Therefore,
|
86
|
+
# rang = Rangeary(?a..?c, positive: ?z)
|
87
|
+
# r = rang + Rangeary(?c..?f, positive: ?y)
|
88
|
+
# r.infinities[:positive] # => ?z (NOT ?y)
|
89
|
+
#
|
90
|
+
# If the old and new infinities are incomparable, the result is uncertain.
|
91
|
+
#
|
92
|
+
# @param other [HashInf, Hash] if it is Hash, converted into HashInf.
|
93
|
+
# @param force [Boolean] if true, other always has a higher priority like +Hash#merge+
|
94
|
+
# unless the corresponding status is +false+, in which case nothing changes.
|
95
|
+
# This is useful to handle the parameters given by the direct arguments to {Rangeary.initialize}
|
96
|
+
# @return [HashInf, Hash]
|
97
|
+
def merge_hashinf(other, force: false)
|
98
|
+
rethsinf = HashInf.construct(self)
|
99
|
+
rethsinf.merge_hashinf!(other, force: force)
|
100
|
+
rethsinf
|
101
|
+
end
|
102
|
+
|
103
|
+
# Destructive version of {#merge_hashinf}
|
104
|
+
#
|
105
|
+
# @param (see #posinega_with_status=)
|
106
|
+
# @return [HashInf, Hash]
|
107
|
+
def merge_hashinf!(other, force: false)
|
108
|
+
if !other.respond_to? :status
|
109
|
+
other = HashInf.construct(other)
|
110
|
+
end
|
111
|
+
|
112
|
+
POSNEG2METHOD.each_key do |posneg|
|
113
|
+
cmped = (force ? 1 : (STATUS_PRIORITIES[self.status[posneg]] <=> STATUS_PRIORITIES[other.status[posneg]]))
|
114
|
+
case cmped
|
115
|
+
when -1
|
116
|
+
# Do nothing
|
117
|
+
when 0, 1
|
118
|
+
next if false == other[posneg] # no change if the status of "other" is false; n.b., when force==true, chances are self[posneg]!=false and other[posneg]==false, because "cmped" for the *case* is modified!
|
119
|
+
if 0 == cmped
|
120
|
+
if false != self[posneg]
|
121
|
+
oper = ((posneg == :negative) ? :> : :<)
|
122
|
+
to_change = _former_more_extreme?(oper, self[posneg], other[posneg])
|
123
|
+
next if !to_change
|
124
|
+
end
|
125
|
+
end
|
126
|
+
set_posinega_with_status(posneg, other[posneg], (force ? :definite : other.status[posneg]))
|
127
|
+
else
|
128
|
+
raise "should not happen. Contact the code developer."
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns true if the former is more extreme.
|
136
|
+
#
|
137
|
+
# If +v1+ is +nil+, returns +true+.
|
138
|
+
# If not and if +v2+ is +nil+, returns +false+.
|
139
|
+
#
|
140
|
+
# The following fails because whereas the guessed positive infinity
|
141
|
+
# from the first one is Float:INFINITY, the second given infinity
|
142
|
+
# is +RangeExtd::Infinity::POSITIVE+, which is not comparable
|
143
|
+
# with +Float:INFINITY+ (although it is comparable with general Float).
|
144
|
+
#
|
145
|
+
# Rangeary(..6, 5..RangeExtd::Infinity::POSITIVE)
|
146
|
+
#
|
147
|
+
# @param oper [Symbol] Method of operation: +return v1 if v1 oper v2+
|
148
|
+
# @param v1 [Object]
|
149
|
+
# @param v2 [Object]
|
150
|
+
# @raise [ArgumentError]
|
151
|
+
def _former_more_extreme?(oper, v1, v2)
|
152
|
+
return true if v1.nil?
|
153
|
+
return false if v2.nil?
|
154
|
+
v1.send(oper, v2)
|
155
|
+
rescue ArgumentError => err
|
156
|
+
raise ArgumentError, "Inconsistent given infinities: "+err.message
|
157
|
+
end
|
158
|
+
private :_former_more_extreme?
|
159
|
+
|
160
|
+
# Recommended way to set (update) the positive infinity with a status with verification
|
161
|
+
#
|
162
|
+
# @param (see #posinega_with_status=)
|
163
|
+
# @return [self]
|
164
|
+
def set_positive_with_status(infinity, stat)
|
165
|
+
set_posinega_with_status(:positive, infinity, stat)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Recommended way to set (update) the negative infinity with a status with verification
|
169
|
+
#
|
170
|
+
# @param (see #posinega_with_status=)
|
171
|
+
# @return [self]
|
172
|
+
def set_negative_with_status(infinity, stat)
|
173
|
+
set_posinega_with_status(:negative, infinity, stat)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Recommended way to set (update) the infinity in either polarity with a status with verification
|
177
|
+
#
|
178
|
+
# @param infinity [Object]
|
179
|
+
# @param stat [Symbol, NilClass] one of {HashInf::STATUS_ORDERS}.
|
180
|
+
# @return [self]
|
181
|
+
def set_posinega_with_status(posneg, infinity, stat)
|
182
|
+
self[posneg] = infinity
|
183
|
+
raise ArgumentError, "stat must be one of #{STATUS_ORDERS.inspect}" if !STATUS_ORDERS.include? stat
|
184
|
+
self.status[posneg] = stat
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# Return true if the infinity of the specified polarity is +:definite+
|
189
|
+
#
|
190
|
+
# @param posneg [Symbol] either :positive or :negative
|
191
|
+
def definite?(posneg)
|
192
|
+
status_is_a?(:definite, posneg)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Return true if the infinity of the specified polarity is +:guessed+
|
196
|
+
#
|
197
|
+
# @param posneg [Symbol] either :positive or :negative
|
198
|
+
def guessed?(posneg)
|
199
|
+
status_is_a?(:guessed, posneg)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Return true if the specified infinity is +nil+.
|
203
|
+
#
|
204
|
+
# @param posneg [Symbol] either :positive or :negative
|
205
|
+
def status_is_nil?(posneg)
|
206
|
+
status_is_a?(nil, posneg)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Return true if the specified infinity is with a specified status.
|
210
|
+
#
|
211
|
+
# @param stat [Symbol, NilClass] One of the elements of {STATUS_ORDERS}
|
212
|
+
# @param posneg [Symbol] either :positive or :negative
|
213
|
+
def status_is_a?(stat, posneg)
|
214
|
+
raise ArgumentError, "Invalid specified status of #{stat.inspect}" if !STATUS_ORDERS.include? stat
|
215
|
+
case posneg
|
216
|
+
when :positive, :negative
|
217
|
+
stat == status[posneg]
|
218
|
+
else
|
219
|
+
raise ArgumentError, "Invalid argument, neither :positive nor :negative: #{posneg.inspect}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# Alter +Hash#inspect+ (Note irb does not take this into account)
|
225
|
+
#
|
226
|
+
# @return [String]
|
227
|
+
def inspect
|
228
|
+
sprintf "<Hash(Inf): %s, status: %s>", super, status
|
229
|
+
end
|
230
|
+
end # module HashInf
|
231
|
+
end # module Util
|
232
|
+
end # class Rangeary < Array
|
233
|
+
|