Class: NumericHash
- Inherits:
-
Hash
- Object
- Hash
- NumericHash
- Defined in:
- lib/numeric_hash.rb,
lib/numeric_hash/version.rb
Overview
Defines a hash whose values are Numeric or additional nested NumericHashes.
Common arithmetic methods available on Numeric can be called on NumericHash to affect all values within the NumericHash at once.
Constant Summary collapse
- DEFAULT_INITIAL_VALUE =
Default initial value for hash values when an initial value is unspecified. Integer 0 is used instead of Float 0.0 because it can automatically be converted into a Float when necessary during operations with other Floats.
0
- BINARY_OPERATORS =
[:+, :-, :*, :/, :%, :**, :&, :|, :^, :div, :modulo, :quo, :fdiv, :remainder]
- UNARY_OPERATORS =
[:+@, :-@, :~@, :abs, :ceil, :floor, :round, :truncate]
- VERSION =
"0.5.2"
Class Method Summary collapse
-
.sum(array) ⇒ Object
Sums an array of NumericHashes, taking into account empty arrays.
Instance Method Summary collapse
- #apply_array!(array, initial_value = DEFAULT_INITIAL_VALUE) ⇒ Object
- #apply_hash!(hash, initial_value = DEFAULT_INITIAL_VALUE) ⇒ Object
-
#collect_numeric(&block) ⇒ Object
(also: #map_numeric)
Maps each numeric value using the specified block.
- #collect_numeric!(&block) ⇒ Object (also: #map_numeric!)
-
#compress ⇒ Object
Compress the hash to its top level values, totaling all nested values.
- #compress! ⇒ Object
-
#deep_merge(other_hash, match_structure = false) ⇒ Object
Performs a merge with another hash while recursively merging any nested hashes.
- #deep_merge!(other_hash, match_structure = false) ⇒ Object
-
#ignore_negatives ⇒ Object
DEPRECATED This method is deprecated.
-
#initialize(initial_contents = nil, initial_value = DEFAULT_INITIAL_VALUE) ⇒ NumericHash
constructor
Initialize the NumericHash with an array of initial keys or hash of initial key-value pairs (whose values could also be arrays or hashes).
-
#max ⇒ Object
Returns the key-value pair with the largest compressed value in the hash.
-
#min ⇒ Object
Returns the key-value pair with the smallest compressed value in the hash.
-
#normalize(magnitude = 1.0) ⇒ Object
Normalize the total of all hash values to the specified magnitude.
- #normalize!(magnitude = 1.0) ⇒ Object
-
#reject_numeric(&block) ⇒ Object
Rejects each numeric value for which the specified block evaluates to true.
- #reject_numeric!(&block) ⇒ Object
-
#select_numeric ⇒ Object
Selects each numeric value for which the specified block evaluates to true.
- #select_numeric! ⇒ Object
-
#strip_zero ⇒ Object
DEPRECATED This method is deprecated.
- #to_amount(amount) ⇒ Object
-
#to_hash ⇒ Object
Converts the NumericHash into a regular Hash.
- #to_percent ⇒ Object
-
#to_ratio ⇒ Object
Shortcuts to normalize the hash to various totals.
-
#total ⇒ Object
Total all values in the hash.
Constructor Details
#initialize(initial_contents = nil, initial_value = DEFAULT_INITIAL_VALUE) ⇒ NumericHash
Initialize the NumericHash with an array of initial keys or hash of initial key-value pairs (whose values could also be arrays or hashes). An optional initial value for initial keys can be specified as well.
NumericHash.new # => { }
NumericHash.new([:a, :b]) # => { :a => 0, :b => 0 }
NumericHash.new([:c, :d], 1.0) # => { :c => 1.0, :d => 1.0 }
NumericHash.new(:e => 2, :f => 3.0) # => { :e => 2, :f => 3.0 }
NumericHash.new({ :g => 4, :h => [:i, :j] }, 5.0) # => { :g => 4, :h => { :i => 5.0, :j => 5.0 } }
28 29 30 31 32 33 34 |
# File 'lib/numeric_hash.rb', line 28 def initialize(initial_contents = nil, initial_value = DEFAULT_INITIAL_VALUE) case initial_contents when ::Array then apply_array!(initial_contents, initial_value) when ::Hash then apply_hash!(initial_contents, initial_value) else raise ArgumentError.new("invalid initial data: #{initial_contents.inspect}") if initial_contents end end |
Class Method Details
.sum(array) ⇒ Object
Sums an array of NumericHashes, taking into account empty arrays.
@array # => [ { :a => 1.0, :b => 2 }, { :a => 3, :c => 4 } ]
sum(@array) # => { :a => 4.0, :b => 2, :c => 4 }
sum([]) # => { }
431 432 433 |
# File 'lib/numeric_hash.rb', line 431 def sum(array) array.empty? ? self.new : array.sum end |
Instance Method Details
#apply_array!(array, initial_value = DEFAULT_INITIAL_VALUE) ⇒ Object
36 37 38 |
# File 'lib/numeric_hash.rb', line 36 def apply_array!(array, initial_value = DEFAULT_INITIAL_VALUE) array.each { |key| self[key] = initial_value } end |
#apply_hash!(hash, initial_value = DEFAULT_INITIAL_VALUE) ⇒ Object
40 41 42 43 44 |
# File 'lib/numeric_hash.rb', line 40 def apply_hash!(hash, initial_value = DEFAULT_INITIAL_VALUE) hash.each do |key, value| self[key] = (value.is_a?(::Array) || value.is_a?(::Hash)) ? NumericHash.new(value, initial_value) : convert_to_numeric(value) end end |
#collect_numeric(&block) ⇒ Object Also known as: map_numeric
Maps each numeric value using the specified block.
@hash # => { :a => 1, :b => { :c => 2, :d => 3 } }
@hash.map_numeric { |value| "X" * value } # => { :a => "X", :b => { :c => "XX", :d => "XXX" } }
@hash.collect_numeric(&:to_s) # => { :a => "1", :b => { :c => "2", :d => "3" } }
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/numeric_hash.rb', line 194 def collect_numeric(&block) # First attempt to map into a NumericHash. map_values do |value| if value.is_a?(NumericHash) result = value.collect_numeric(&block) else result = yield(value) # If the mapped value not Numeric, abort so that we try again by # mapping into a regular Hash. raise TypeError.new("result is not Numeric: #{result.inspect}") unless result.is_a?(Numeric) end result end rescue TypeError # At least one of the values mapped into a non-Numeric result; map into a # regular Hash instead. map_to_hash do |key, value| [key, value.is_a?(NumericHash) ? value.collect_numeric(&block) : yield(value) ] end end |
#collect_numeric!(&block) ⇒ Object Also known as: map_numeric!
217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/numeric_hash.rb', line 217 def collect_numeric!(&block) map_values! do |value| if value.is_a?(NumericHash) result = value.collect_numeric!(&block) else result = yield(value) # If the mapped value not Numeric, abort since we can't change a # NumericHash into a regular Hash. raise TypeError.new("result is not Numeric: #{result.inspect}") unless result.is_a?(Numeric) end result end end |
#compress ⇒ Object
Compress the hash to its top level values, totaling all nested values.
@hash # => { :a => 1, :b => { :c => 2.0, d: => 3 } }
@hash.compress # => { :a => 1, :b => 5.0 }
62 63 64 |
# File 'lib/numeric_hash.rb', line 62 def compress map_values { |value| convert_to_numeric(value) } end |
#compress! ⇒ Object
66 67 68 |
# File 'lib/numeric_hash.rb', line 66 def compress! map_values! { |value| convert_to_numeric(value) } end |
#deep_merge(other_hash, match_structure = false) ⇒ Object
Performs a merge with another hash while recursively merging any nested hashes. If true is specified as a second argument, the merge will ensure that the key structure of the other hash is a subset of the structure of the hash.
@hash1 # => { :a => 1, :b => { :c => 2 } }
@hash2 # => { :b => 3 }
@hash3 # => { :d => 4 }
@hash1.deep_merge(@hash2) # => { :a => 1, :b => 3 }
@hash1.deep_merge(@hash2, true) # raises TypeError
@hash1.deep_merge(@hash3) # => { :a => 1, :b => { :c => 2 }, :d => 4 }
@hash1.deep_merge(@hash3, true) # raises TypeError
292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/numeric_hash.rb', line 292 def deep_merge(other_hash, match_structure = false) raise ArgumentError.new('hash must be specified') unless other_hash.is_a?(::Hash) raise TypeError.new('structure of specified hash is incompatible') if match_structure && !compatible_structure?(other_hash) other_hash.inject(self.copy) do |hash, (key, value)| hash[key] = if hash[key].is_a?(NumericHash) && value.is_a?(::Hash) hash[key].deep_merge(value, match_structure) else sanitize_numeric_hash_value(value) end hash end end |
#deep_merge!(other_hash, match_structure = false) ⇒ Object
306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/numeric_hash.rb', line 306 def deep_merge!(other_hash, match_structure = false) raise ArgumentError.new('hash not specified') unless other_hash.is_a?(::Hash) raise TypeError.new('structure of specified hash is incompatible') if match_structure && !compatible_structure?(other_hash) other_hash.each do |key, value| if self[key].is_a?(NumericHash) && value.is_a?(::Hash) self[key].deep_merge!(value, match_structure) else self[key] = sanitize_numeric_hash_value(value) end end self end |
#ignore_negatives ⇒ Object
DEPRECATED This method is deprecated. Consider using #map_numeric to perform the same function.
Set all negative values in the hash to zero.
@hash # => { :a => -0.6, :b => 1.2, :c => 0.4 }
@hash.ignore_negatives # => { :a => 0.0, :b => 1.2, :a => 0.4 }
121 122 123 124 |
# File 'lib/numeric_hash.rb', line 121 def ignore_negatives warn "DEPRECATION WARNING: This method is deprecated. Consider using #map_numeric to perform the same function. Called from: #{caller.first}" convert_negatives_to_zero(self) end |
#max ⇒ Object
Returns the key-value pair with the largest compressed value in the hash.
109 110 111 |
# File 'lib/numeric_hash.rb', line 109 def max compressed_key_values_sorted.last end |
#min ⇒ Object
Returns the key-value pair with the smallest compressed value in the hash.
103 104 105 |
# File 'lib/numeric_hash.rb', line 103 def min compressed_key_values_sorted.first end |
#normalize(magnitude = 1.0) ⇒ Object
Normalize the total of all hash values to the specified magnitude. If no magnitude is specified, the hash is normalized to 1.0.
@hash # => { :a => 1, :b => 2, :c => 3, :d => 4 }
@hash.normalize # => { :a => 0.1, :b => 0.2, :c => 0.3, :d => 0.4 }
@hash.normalize(120) # => { :a => 12.0, :b => 24.0, :c => 36.0, :d => 48.0 }
77 78 79 80 |
# File 'lib/numeric_hash.rb', line 77 def normalize(magnitude = 1.0) norm_factor = normalization_factor(magnitude) map_values { |value| value * norm_factor } end |
#normalize!(magnitude = 1.0) ⇒ Object
82 83 84 85 |
# File 'lib/numeric_hash.rb', line 82 def normalize!(magnitude = 1.0) norm_factor = normalization_factor(magnitude) map_values! { |value| value * norm_factor } end |
#reject_numeric(&block) ⇒ Object
Rejects each numeric value for which the specified block evaluates to true. Any nested hashes that become empty during this procedure are also rejected.
@hash # => { :a => 1, :b => 0.0, :c => { :d => 0, :e => -2 }, :f => { :g => 0.0 } }
@hash.reject_numeric(&:zero?) # => { :a => 1, :c => { :e => -2 } }
@hash.reject_numeric { |value| value <= 0 } # => { :a => 1 }
241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/numeric_hash.rb', line 241 def reject_numeric(&block) inject_into_empty do |hash, (key, value)| if value.is_a?(NumericHash) rejected = value.reject_numeric(&block) hash[key] = rejected unless rejected.empty? elsif !yield(value) hash[key] = value end hash end end |
#reject_numeric!(&block) ⇒ Object
253 254 255 256 257 258 259 260 261 262 |
# File 'lib/numeric_hash.rb', line 253 def reject_numeric!(&block) reject_values! do |value| if value.is_a?(NumericHash) value.reject_values!(&block) value.empty? else yield(value) end end end |
#select_numeric ⇒ Object
Selects each numeric value for which the specified block evaluates to true. Any nested hashes with no selected values will not be included.
@hash # => { :a => 1, :b => 0.0, :c => { :d => 0, :e => -2 }, :f => { :g => 0.0 } }
@hash.select_numeric(&:zero?) # => { :b => 0.0, :c => { :d => 0 }, :f => { :g => 0.0 } }
@hash.select_numeric { |value| value <= 0 } # => { :b => 0.0, :c => { :d => 0, :e => -2 }, :f => { :g => 0.0 } }
271 272 273 |
# File 'lib/numeric_hash.rb', line 271 def select_numeric reject_numeric { |value| !yield(value) } end |
#select_numeric! ⇒ Object
275 276 277 |
# File 'lib/numeric_hash.rb', line 275 def select_numeric! reject_numeric! { |value| !yield(value) } end |
#strip_zero ⇒ Object
DEPRECATED This method is deprecated. Consider using #compress with #reject_numeric to perform the same function.
Strips out any zero valued asset classes.
@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}
@hash.strip_zero # => {:c => 0.8, :e => 0.05, :d => 0.15}
134 135 136 137 138 |
# File 'lib/numeric_hash.rb', line 134 def strip_zero warn "DEPRECATION WARNING: This method is deprecated. Consider using #compress with #reject_numeric to perform the same function. Called from: #{caller.first}" # 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. compress.select_values! { |value| value > 0.0 } end |
#to_amount(amount) ⇒ Object
97 98 99 |
# File 'lib/numeric_hash.rb', line 97 def to_amount(amount) normalize(amount) end |
#to_hash ⇒ Object
Converts the NumericHash into a regular Hash.
322 323 324 325 326 |
# File 'lib/numeric_hash.rb', line 322 def to_hash map_to_hash do |key, value| [key, value.is_a?(NumericHash) ? value.to_hash : value] end end |
#to_percent ⇒ Object
93 94 95 |
# File 'lib/numeric_hash.rb', line 93 def to_percent normalize(100.0) end |
#to_ratio ⇒ Object
Shortcuts to normalize the hash to various totals.
89 90 91 |
# File 'lib/numeric_hash.rb', line 89 def to_ratio normalize(1.0) end |
#total ⇒ Object
Total all values in the hash.
@hash1 # => { :a => 1.0, :b => 2 }
@hash2 # => { :c => 3, :d => { :e => 4, :f => 5} }
@hash1.total # => 3.0
@hash2.total # => 12
53 54 55 |
# File 'lib/numeric_hash.rb', line 53 def total values.map { |value| convert_to_numeric(value) }.sum end |