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.
@@ -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