Skip to content

Instantly share code, notes, and snippets.

@lucasmartins
Last active August 29, 2015 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lucasmartins/03a5dafe1517d8b24a17 to your computer and use it in GitHub Desktop.
Save lucasmartins/03a5dafe1517d8b24a17 to your computer and use it in GitHub Desktop.
Recursive Hash Reducer
module HashReducer
# use the block to transform the addition/reduce result for each value
def self.reduce!(base: nil, add: nil, &block)
base = {} unless base
add = {} unless add
fail 'base: must be a hash!' unless base.is_a?(Hash)
fail 'add: must be a hash!' unless add.is_a?(Hash)
add.each do |k,v|
if v.is_a?(Hash)
base[k] = {} unless base[k]
base[k] = self.reduce!(base: base[k], add: v, &block)
else
initialize_value_field!(base, add, k)
base[k] += add[k] if add[k].respond_to?(:+)
if block_given?
base[k] = block.call(base[k])
end
end
end
base
end
private
def self.initialize_value_field!(base, add, k)
unless base[k]
if add[k].respond_to?(:new)
base[k] = add[k].class.new
elsif add[k].respond_to?(:+)
base[k] = 0
else
fail "Don't know how to initialize a #{add[k].class} object!"
end
end
end
end
require 'spec_helper'
describe HashReducer do
describe '.reduce!' do
let(:reducer) { HashReducer }
let(:hash_a) { { a: 1, b: 2, c: { d: 3, e: { f: 4, g: 5 } } } }
let(:hash_b) { { a: 1, b: 2, c: { d: 3 } } }
let(:hash_c) { { a: 1.9999999999, b: 2.9999999999, c: { d: 3.9999999999, e: { f: 0.1111111111, g: 5.9999999999 } } } }
let(:hash_sum) { { a: 2, b: 4, c: { d: 6, e: { f: 8, g: 10 } } } }
it 'merges a hash into another' do
expect(reducer.reduce!(base: hash_a, add: hash_a)).to eq(hash_sum)
end
it 'merges a hash into an empty' do
expect(reducer.reduce!(base: hash_a, add: Hash.new)).to eq(hash_a)
end
context 'additional' do
let(:hash_bc) { { a: 2, b: 4, c: { d: 6, e: { f: 4, g: 5 } } } }
it 'merges a hash into another with a different scheme' do
expect(reducer.reduce!(base: hash_b, add: hash_a)).to eq(hash_bc)
end
end
context 'with block' do
it 'rounds the result' do
result = reducer.reduce!(base: hash_a, add: hash_c) do |sum|
sum.round(2)
end
expect(result).to match_json('reduce_with_block')
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment