numeric_hash 0.3.1 → 0.4.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.
- 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
|