Mischa said:
Hi,
In Ruby, when I see something like:
h = []
x.each do
...
end
h
Usually means that I can refactor it to use map, inject, select, or
reject.
Yes, I do the same. In a broader sense, functional style is often
shorter and less error-prone. Map, inject, select are just examples
of the functional idiom smuggled into ruby.
In this case, a hash-of-hash merge function is needed to reorder keys
in a clean way.
#
# hoh_merge(
# { :x => { :y => :foo } },
# { :x => { :z => :bar } }
# )
# #=> {:x=>{:y=>:foo, :z=>:bar}}
#
def hoh_merge(a, b)
b.inject(a) { |acc, (key, inner_hash)|
acc.merge(
key => (
if existing = a[key]
existing.merge(inner_hash)
else
inner_hash
end
)
)
}
end
data = {
:flintstone => {
:husband => :fred,
:wife => :wilma,
},
:rubble => {
:husband => :barney,
:wife => :betty,
},
}
# original imperative version
h = Hash.new { |hash, key| hash[key] = Hash.new }
data.each { |hash_key, inner_hash|
inner_hash.each { |key, value|
h[key][hash_key] = value
}
}
# pure functional version
h2 = data.inject(Hash.new) { |acc, (hash_key, inner_hash)|
inner_hash.keys.inject(acc) { |inner_acc, key|
hoh_merge(inner_acc, key => { hash_key => inner_hash[key] })
}
}
require 'pp'
pp h
pp h2
#=>
# {:wife=>{:rubble=>:betty, :flintstone=>:wilma},
# :husband=>{:rubble=>:barney, :flintstone=>:fred}}
# {:wife=>{:rubble=>:betty, :flintstone=>:wilma},
# :husband=>{:rubble=>:barney, :flintstone=>:fred}}
I wrote a purely-functional hoh_merge just for fun. An imperative
method would be more efficient (modifying its first argument),
h2 = data.inject(Hash.new) { |acc, (hash_key, inner_hash)|
inner_hash.keys.inject(acc) { |inner_acc, key|
hoh_merge!(inner_acc, key => { hash_key => inner_hash[key] })
}
}
But here inner_acc is always identical to acc, making the inject just
for show. You're back to using #each.
I guess the moral of the story is that functional style in ruby
realistically applies only to flat arrays and hashes. Once we reach
the hash-of-hashes realm, imperative constructs are more suitable.
Lazy evaluation is needed to be efficient, recursive, and purely
functional all at the same time. (See Haskell.)