Forum: Ruby Help using ruby enumerator idioms

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Ff937b6907db49432c980f2b6a5c7e71?d=identicon&s=25 Mischa Fierer (mischa)
on 2009-01-01 13:39
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.

However, I am unsure how to do this for the code below:



         h = Hash.new {|hash, key| hash[key] = {}}

   some_hash_of_hashes.each do |hash_key, sub_hash|
     sub_hash.each {|key, value|   h[key][hash_key] = value }
   end

   h


Let me know if anyone has any ideas.

M
A246f7c0ce5f2909483d358bd9e83e4e?d=identicon&s=25 Mike Gold (mikegold)
on 2009-01-02 09:34
Mischa Fierer wrote:
> 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.)
Ff937b6907db49432c980f2b6a5c7e71?d=identicon&s=25 Mischa Fierer (mischa)
on 2009-01-02 10:47
> 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.)

Interesting, thanks a ton for the reply.

I think you may be right that for hashes of hashes imperative style Ruby
starts to make more sense.

This has been hitting me a lot lately, as one of the projects I'm
working on has a lot of hashes of hashes (data crunching). I used the
inject style a few times, but as you note, it becomes mostly just for
show.

M
This topic is locked and can not be replied to.