numeric_hash 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/numeric_hash/version.rb +1 -1
- data/lib/numeric_hash.rb +149 -22
- metadata +5 -5
data/lib/numeric_hash/version.rb
CHANGED
data/lib/numeric_hash.rb
CHANGED
@@ -5,7 +5,7 @@ require "numeric_hash/version"
|
|
5
5
|
# Common arithmetic methods available on Numeric can be called on NumericHash
|
6
6
|
# to affect all values within the NumericHash at once.
|
7
7
|
#
|
8
|
-
class NumericHash < Hash
|
8
|
+
class NumericHash < ::Hash
|
9
9
|
|
10
10
|
# Default initial value for hash values when an initial value is unspecified.
|
11
11
|
# Integer 0 is used instead of Float 0.0 because it can automatically be
|
@@ -27,8 +27,8 @@ class NumericHash < Hash
|
|
27
27
|
#
|
28
28
|
def initialize(initial_contents = nil, initial_value = DEFAULT_INITIAL_VALUE)
|
29
29
|
case initial_contents
|
30
|
-
when Array then apply_array!(initial_contents, initial_value)
|
31
|
-
when Hash then apply_hash!(initial_contents, initial_value)
|
30
|
+
when ::Array then apply_array!(initial_contents, initial_value)
|
31
|
+
when ::Hash then apply_hash!(initial_contents, initial_value)
|
32
32
|
else raise ArgumentError.new("invalid initial data: #{initial_contents.inspect}") if initial_contents
|
33
33
|
end
|
34
34
|
end
|
@@ -39,7 +39,7 @@ class NumericHash < Hash
|
|
39
39
|
|
40
40
|
def apply_hash!(hash, initial_value = DEFAULT_INITIAL_VALUE)
|
41
41
|
hash.each do |key, value|
|
42
|
-
self[key] = (value.is_a?(Array) || value.is_a?(Hash)) ? NumericHash.new(value, initial_value) : convert_to_numeric(value)
|
42
|
+
self[key] = (value.is_a?(::Array) || value.is_a?(::Hash)) ? NumericHash.new(value, initial_value) : convert_to_numeric(value)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -106,25 +106,33 @@ class NumericHash < Hash
|
|
106
106
|
compressed_key_values_sorted.last
|
107
107
|
end
|
108
108
|
|
109
|
+
# *DEPRECATED* This method is deprecated. Consider using #map_numeric to
|
110
|
+
# perform the same function.
|
111
|
+
#
|
109
112
|
# Set all negative values in the hash to zero.
|
110
113
|
#
|
111
114
|
# @hash # => { :a => -0.6, :b => 1.2, :c => 0.4 }
|
112
115
|
# @hash.ignore_negatives # => { :a => 0.0, :b => 1.2, :a => 0.4 }
|
113
116
|
#
|
114
117
|
def ignore_negatives
|
118
|
+
warn "DEPRECATION WARNING: This method is deprecated. Consider using #map_numeric to perform the same function. Called from: #{caller.first}"
|
115
119
|
convert_negatives_to_zero(self)
|
116
120
|
end
|
117
121
|
|
122
|
+
# *DEPRECATED* This method is deprecated. Consider using #compress with
|
123
|
+
# #reject_numeric to perform the same function.
|
124
|
+
#
|
118
125
|
# Strips out any zero valued asset classes.
|
119
126
|
#
|
120
127
|
# @hash # => {:a => 0.0, :b => 0.0, :c => 0.8, :d => 0.15, :e => 0.05, :f => 0.0, :g => 0.0, :h => 0.0, :i => 0.0}
|
121
128
|
# @hash.strip_zero # => {:c => 0.8, :e => 0.05, :d => 0.15}
|
122
129
|
#
|
123
130
|
def strip_zero
|
131
|
+
warn "DEPRECATION WARNING: This method is deprecated. Consider using #compress with #reject_numeric to perform the same function. Called from: #{caller.first}"
|
124
132
|
# TODO: Previous version of the code only retained values > 0.0, so the refactored code below retains this behavior; verify whether this is still desired.
|
125
133
|
compress.select_values! { |value| value > 0.0 }
|
126
134
|
end
|
127
|
-
|
135
|
+
|
128
136
|
# Define arithmetic operators that apply a Numeric or another NumericHash to
|
129
137
|
# the hash. A Numeric argument is applied to each value in the hash.
|
130
138
|
# Hash values of a NumericHash argument are applied to each corresponding
|
@@ -181,7 +189,7 @@ class NumericHash < Hash
|
|
181
189
|
#
|
182
190
|
def collect_numeric(&block)
|
183
191
|
# First attempt to map into a NumericHash.
|
184
|
-
|
192
|
+
map_values do |value|
|
185
193
|
if value.is_a?(NumericHash)
|
186
194
|
result = value.collect_numeric(&block)
|
187
195
|
else
|
@@ -189,9 +197,9 @@ class NumericHash < Hash
|
|
189
197
|
|
190
198
|
# If the mapped value not Numeric, abort so that we try again by
|
191
199
|
# mapping into a regular Hash.
|
192
|
-
raise TypeError.new(
|
200
|
+
raise TypeError.new("result is not Numeric: #{result.inspect}") unless result.is_a?(Numeric)
|
193
201
|
end
|
194
|
-
|
202
|
+
result
|
195
203
|
end
|
196
204
|
rescue TypeError
|
197
205
|
# At least one of the values mapped into a non-Numeric result; map into a
|
@@ -202,6 +210,109 @@ class NumericHash < Hash
|
|
202
210
|
end
|
203
211
|
alias_method :map_numeric, :collect_numeric
|
204
212
|
|
213
|
+
def collect_numeric!(&block)
|
214
|
+
map_values! do |value|
|
215
|
+
if value.is_a?(NumericHash)
|
216
|
+
result = value.collect_numeric!(&block)
|
217
|
+
else
|
218
|
+
result = yield(value)
|
219
|
+
|
220
|
+
# If the mapped value not Numeric, abort since we can't change a
|
221
|
+
# NumericHash into a regular Hash.
|
222
|
+
raise TypeError.new("result is not Numeric: #{result.inspect}") unless result.is_a?(Numeric)
|
223
|
+
end
|
224
|
+
result
|
225
|
+
end
|
226
|
+
end
|
227
|
+
alias_method :map_numeric!, :collect_numeric!
|
228
|
+
|
229
|
+
# Rejects each numeric value for which the specified block evaluates to true.
|
230
|
+
# Any nested hashes that become empty during this procedure are also
|
231
|
+
# rejected.
|
232
|
+
#
|
233
|
+
# @hash # => { :a => 1, :b => 0.0, :c => { :d => 0, :e => -2 }, :f => { :g => 0.0 } }
|
234
|
+
# @hash.reject_numeric(&:zero?) # => { :a => 1, :c => { :e => -2 } }
|
235
|
+
# @hash.reject_numeric { |value| value <= 0 } # => { :a => 1 }
|
236
|
+
#
|
237
|
+
def reject_numeric(&block)
|
238
|
+
inject_into_empty do |hash, (key, value)|
|
239
|
+
if value.is_a?(NumericHash)
|
240
|
+
rejected = value.reject_numeric(&block)
|
241
|
+
hash[key] = rejected unless rejected.empty?
|
242
|
+
elsif !yield(value)
|
243
|
+
hash[key] = value
|
244
|
+
end
|
245
|
+
hash
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def reject_numeric!(&block)
|
250
|
+
reject_values! do |value|
|
251
|
+
if value.is_a?(NumericHash)
|
252
|
+
value.reject_values!(&block)
|
253
|
+
value.empty?
|
254
|
+
else
|
255
|
+
yield(value)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Selects each numeric value for which the specified block evaluates to true.
|
261
|
+
# Any nested hashes with no selected values will not be included.
|
262
|
+
#
|
263
|
+
# @hash # => { :a => 1, :b => 0.0, :c => { :d => 0, :e => -2 }, :f => { :g => 0.0 } }
|
264
|
+
# @hash.select_numeric(&:zero?) # => { :b => 0.0, :c => { :d => 0 }, :f => { :g => 0.0 } }
|
265
|
+
# @hash.select_numeric { |value| value <= 0 } # => { :b => 0.0, :c => { :d => 0, :e => -2 }, :f => { :g => 0.0 } }
|
266
|
+
#
|
267
|
+
def select_numeric
|
268
|
+
reject_numeric { |value| !yield(value) }
|
269
|
+
end
|
270
|
+
|
271
|
+
def select_numeric!
|
272
|
+
reject_numeric! { |value| !yield(value) }
|
273
|
+
end
|
274
|
+
|
275
|
+
# Performs a merge with another hash while recursively merging any nested
|
276
|
+
# hashes. If true is specified as a second argument, the merge will ensure
|
277
|
+
# that the key structure of the other hash is a subset of the structure of
|
278
|
+
# the hash.
|
279
|
+
#
|
280
|
+
# @hash1 # => { :a => 1, :b => { :c => 2 } }
|
281
|
+
# @hash2 # => { :b => 3 }
|
282
|
+
# @hash3 # => { :d => 4 }
|
283
|
+
# @hash1.deep_merge(@hash2) # => { :a => 1, :b => 3 }
|
284
|
+
# @hash1.deep_merge(@hash2, true) # raises TypeError
|
285
|
+
# @hash1.deep_merge(@hash3) # => { :a => 1, :b => { :c => 2 }, :d => 4 }
|
286
|
+
# @hash1.deep_merge(@hash3, true) # raises TypeError
|
287
|
+
#
|
288
|
+
def deep_merge(other_hash, match_structure = false)
|
289
|
+
raise ArgumentError.new('hash must be specified') unless other_hash.is_a?(::Hash)
|
290
|
+
raise TypeError.new('structure of specified hash is incompatible') if match_structure && !compatible_structure?(other_hash)
|
291
|
+
|
292
|
+
other_hash.inject(self.copy) do |hash, (key, value)|
|
293
|
+
hash[key] = if hash[key].is_a?(NumericHash) && value.is_a?(::Hash)
|
294
|
+
hash[key].deep_merge(value, match_structure)
|
295
|
+
else
|
296
|
+
sanitize_numeric_hash_value(value)
|
297
|
+
end
|
298
|
+
hash
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def deep_merge!(other_hash, match_structure = false)
|
303
|
+
raise ArgumentError.new('hash not specified') unless other_hash.is_a?(::Hash)
|
304
|
+
raise TypeError.new('structure of specified hash is incompatible') if match_structure && !compatible_structure?(other_hash)
|
305
|
+
|
306
|
+
other_hash.each do |key, value|
|
307
|
+
if self[key].is_a?(NumericHash) && value.is_a?(::Hash)
|
308
|
+
self[key].deep_merge!(value, match_structure)
|
309
|
+
else
|
310
|
+
self[key] = sanitize_numeric_hash_value(value)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
self
|
314
|
+
end
|
315
|
+
|
205
316
|
# Converts the NumericHash into a regular Hash.
|
206
317
|
#
|
207
318
|
def to_hash
|
@@ -227,20 +338,24 @@ protected
|
|
227
338
|
# Helper method for converting a specified value to a Numeric.
|
228
339
|
#
|
229
340
|
def convert_to_numeric(value)
|
230
|
-
|
231
|
-
value.total
|
232
|
-
|
233
|
-
value
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
value.
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
341
|
+
case
|
342
|
+
when value.is_a?(NumericHash) then value.total
|
343
|
+
when value.is_a?(Numeric) then value
|
344
|
+
when value.nil? then DEFAULT_INITIAL_VALUE
|
345
|
+
when value.respond_to?(:to_f) then value.to_f
|
346
|
+
when value.respond_to?(:to_i) then value.to_i
|
347
|
+
when value.respond_to?(:to_int) then value.to_int
|
348
|
+
else raise TypeError.new("cannot convert to Numeric: #{value.inspect}")
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Helper method for sanitizing a value to be placed into a NumericHash.
|
353
|
+
#
|
354
|
+
def sanitize_numeric_hash_value(value)
|
355
|
+
case value
|
356
|
+
when NumericHash then value
|
357
|
+
when Hash then NumericHash.new(value)
|
358
|
+
else convert_to_numeric(value)
|
244
359
|
end
|
245
360
|
end
|
246
361
|
|
@@ -272,6 +387,18 @@ protected
|
|
272
387
|
compress.sort_by { |key, value| value }
|
273
388
|
end
|
274
389
|
|
390
|
+
# Helper method that determines whether the structure of the specified hash
|
391
|
+
# is a subset of the structure of the hash.
|
392
|
+
#
|
393
|
+
def compatible_structure?(other_hash)
|
394
|
+
other_hash.all? do |key, value|
|
395
|
+
self.has_key?(key) && (
|
396
|
+
(!value.is_a?(::Hash) && !self[key].is_a?(NumericHash)) ||
|
397
|
+
(value.is_a?(::Hash) && self[key].is_a?(NumericHash) && self[key].compatible_structure?(value))
|
398
|
+
)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
275
402
|
# Helper method for reconciling traits from another hash when a binary
|
276
403
|
# operation is performed with that hash.
|
277
404
|
#
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: numeric_hash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Clyde Law
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-02-
|
18
|
+
date: 2012-02-22 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|