I'm building a hash with keys available at runtime (so the size of the object isn't known beforehand). I want all these values to be a new instance of a class ContestStanding
, but not the exact same instance. I've achieved this with
h = Hash.new {|h,k| h[k] = ContestStanding.new}
@my_keys.map {|k| h[k]}
h #=> {1=>#<ContestStanding...>, 2=>#<ContestStanding...>, ...}
I'm wondering if there's a way I could do this using Enums or Lambdas like the following. Note: I've verified this does not work. This is just my thought process
Hash[@my_keys.zip(-> { ContestStanding.new })]
Here, the problem is my Lambda isn't enumerable. Is there something like an infinite generator in Ruby?
EDIT
I initially got really tripped up by that Enumerable#each_with_object
method. Didn't see the order of k
and h
in the block parameters. Thought I was going crazy! As for your suggested implementation, when I run in IRB, this is what I get
my_keys = [1,2,3]
my_keys.each_with_object({}) {|k,h| h[k] = 'a'}
#=> {1=>"a", 2=>"a", 3=>"a"}
# The above is what I want to get out of the implementation
Hash[my_keys.zip(Array.new(my_keys.size, Hash.new {|h,k| h[k] = 'a'}))]
#=> {1=>{}, 2=>{}, 3=>{}}
I'm not looking for a Hash of Hashes. That seems to be what the implementation is returning. I'm wanting to get back {1=>'a', 2=>'a', 3=>'a'}
. Any thoughts on that?
Brad,
Here are two ways you could produce the hash. I will use the following as an example:
class ContestStanding
def checkit
puts "hi"
end
end
my_keys = [1,2,3]
Use Enumerable#each_with_object
h = my_keys.each_with_object({}) { |k,h| h[k] = ContestStanding.new }
#=> {1=>#<ContestStanding:0x000001010efdd8>,
# 2=>#<ContestStanding:0x000001010efdb0>,
# 3=>#<ContestStanding:0x000001010efd88>}
h[1].checkit #=> "hi"
each_with_object
creates and empty array which is referenced by the block parameter h
. The first value passed into the block (and assigned to the block parameter k
) is my_keys.first => 1
, so have
h[1] = ContestStanding.new
The other elements of the hash are created similarly.
Use Array.zip
Hash[my_keys.zip(Array.new(my_keys.size) {ContestStanding.new})]
#=> {1=>#<ContestStanding:0x0000010280f720>,
# 2=>#<ContestStanding:0x0000010280f6f8>,
# 3=>#<ContestStanding:0x0000010280f6d0>}
or, for Ruby v2.0+
my_keys.zip(Array.new(my_keys.size) {ContestStanding.new}).to_h
#=> {1=>#<ContestStanding:0x0000010184bd48>,
# 2=>#<ContestStanding:0x0000010184bd20>,
# 3=>#<ContestStanding:0x0000010184bcf8>}
Here the following steps are performed:
a = Array.new(my_keys.size) {ContestStanding.new}
#=> [#<ContestStanding:0x0000010185b248>,
# #<ContestStanding:0x0000010185b220>,
# #<ContestStanding:0x0000010185b1f8>]
b = my_keys.zip(a)
#=> [[1, #<ContestStanding:0x0000010185b248>],
# [2, #<ContestStanding:0x0000010185b220>],
# [3, #<ContestStanding:0x0000010185b1f8>]]
b.to_h
#=> {1=>#<ContestStanding:0x0000010185b248>,
# 2=>#<ContestStanding:0x0000010185b220>,
# 3=>#<ContestStanding:0x0000010185b1f8>}
Your solution
I found your solution interesting. This is one one way of explaining how it works:
enum = Enumerator.new { |y| loop { y << ContestStanding.new } }
#=> #<Enumerator: #<Enumerator::Generator:0x000001011a9530>:each>
a1 = my_keys.size.times.with_object([]) { |k,a| a << enum.next }
#=> [#<ContestStanding:0x000001018820a0>,
# #<ContestStanding:0x00000101882028>,
# #<ContestStanding:0x00000101881fb0>
a2 = my_keys.zip(a1)
#=> [[1, #<ContestStanding:0x000001018820a0>],
# [2, #<ContestStanding:0x00000101882028>],
# [3, #<ContestStanding:0x00000101881fb0>]]
Hash[a2]
#=> {1=>#<ContestStanding:0x000001018820a0>,
# 2=>#<ContestStanding:0x00000101882028>,
# 3=>#<ContestStanding:0x00000101881fb0>}