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.
@@ -1,3 +1,3 @@
1
1
  class NumericHash < Hash
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
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
- map_to_hash(NumericHash) do |key, value|
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('result is not Numeric') unless result.is_a?(Numeric)
200
+ raise TypeError.new("result is not Numeric: #{result.inspect}") unless result.is_a?(Numeric)
193
201
  end
194
- [key, result]
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
- if value.is_a?(NumericHash)
231
- value.total
232
- elsif value.is_a?(Numeric)
233
- value
234
- elsif value.nil?
235
- DEFAULT_INITIAL_VALUE
236
- elsif value.respond_to?(:to_f)
237
- value.to_f
238
- elsif value.respond_to?(:to_i)
239
- value.to_i
240
- elsif value.respond_to?(:to_int)
241
- value.to_int
242
- else
243
- raise TypeError.new("cannot convert to Numeric: #{value.inspect}")
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: 17
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 1
10
- version: 0.3.1
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-21 00:00:00 -08:00
18
+ date: 2012-02-22 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency