Build hash by iterating


#1

I am building a hash this way:

h = {}
i = 0
j = 24

while i < 15 do
h[i] = j
i += 1
j += 6
end

=> {5=>54, 11=>90, 0=>24, 6=>60, 12=>96, 1=>30, 7=>66, 13=>102, 2=>36,
8=>72, 14=>108, 3=>42, 9=>78, 4=>48, 10=>84}

Just what I need, but doesn’t seem very Ruby-friendly

And if I want to change each hash value from fixnum to string, how would
that be done?

this is not working: new_hash = h.each_key {|k| h[k].to_s}

thank you!


#2

On Nov 19, 2008, at 8:31 AM, Jason L. wrote:

end
this is not working: new_hash = h.each_key {|k| h[k].to_s}

thank you!

Posted via http://www.ruby-forum.com/.

i might do

cfp:~ > cat a.rb
h = {} and 15.times{|i| h[i] = (24 + (i * 6))}

p h.sort

p({5=>54, 11=>90, 0=>24, 6=>60, 12=>96, 1=>30, 7=>66, 13=>102, 2=>36,
8=>72, 14=>108, 3=>42, 9=>78, 4=>48, 10=>84}.sort)

cfp:~ > ruby a.rb
[[0, 24], [1, 30], [2, 36], [3, 42], [4, 48], [5, 54], [6, 60], [7,
66], [8, 72], [9, 78], [10, 84], [11, 90], [12, 96], [13, 102], [14,
108]]
[[0, 24], [1, 30], [2, 36], [3, 42], [4, 48], [5, 54], [6, 60], [7,
66], [8, 72], [9, 78], [10, 84], [11, 90], [12, 96], [13, 102], [14,
108]]

a @ http://codeforpeople.com/


#3

h = {}
i = 0
j = 24

while i < 15 do
h[i] = j
i += 1
j += 6
end

h = (0…15).inject({}) do |m,i|
m[i] = 24 + i * 6
m
end

this is not working: new_hash = h.each_key {|k| h[k].to_s}

new_hash = h.inject({}) { |m,p| m[p[0].to_s] = p[1]; m }


#4

On 19.11.2008, at 15:31 , Jason L. wrote:

end
this is not working: new_hash = h.each_key {|k| h[k].to_s}

thank you!

Posted via http://www.ruby-forum.com/.

h = (0…15).inject({}) {|h, i| h[i] = (24+i*6).to_s; h}
p h

but this hash makes no sense.

just do

h = Array.new(15) {|i| (24+i*6).to_s}
p h

#then you can index the array:
p h[11]

p h[5]

>> {5=>“54”, 11=>“90”, 0=>“24”, 6=>“60”, 12=>“96”, 1=>“30”, 7=>“66”,

13=>“102”, 2=>“36”, 8=>“72”, 14=>“108”, 3=>“42”, 9=>“78”, 4=>“48”,
10=>“84”}

>> [“24”, “30”, “36”, “42”, “48”, “54”, “60”, “66”, “72”, “78”,

“84”, “90”, “96”, “102”, “108”]

>> “90”

>> “54”

einarmagnus


#5

perfect. Thank you.


#6

Thank you. Enumerable#inject has always been scary to me. I’m having a
hard time following what you did here. Luckily, I found this great page:
http://blog.jayfields.com/2008/03/ruby-inject.html


#7

And if I want to change each hash value from fixnum to string, how would
that be done?

this is not working: new_hash = h.each_key {|k| h[k].to_s}

The simple two-liner is this:

new_hash = {}
h.each { |k,v| new_hash[k] = v.to_s }

The one-liner uses ‘inject’:

new_hash = h.inject({}) { |n,(k,v)| n[k] = v.to_s; n }

To understand ‘inject’, note that:

  1. you pass in the new object for accumulating the result ({})
  2. for each call of the block, the accumulator is passed as
    the first argument (n)
  3. the return value of the block is used as the new accumulator
    (for the next iteration, or as the final result). In this
    case, we wish to continue using n, so we return that as the
    value of the block.

#8

From: Jason L. [mailto:removed_email_address@domain.invalid]

I am building a hash this way:

h = {}

i = 0

j = 24

while i < 15 do

h[i] = j

i += 1

j += 6

end

=> {5=>54, 11=>90, 0=>24, 6=>60, 12=>96, 1=>30, 7=>66,

13=>102, 2=>36, 8=>72, 14=>108, 3=>42, 9=>78, 4=>48, 10=>84}

Just what I need, but doesn’t seem very Ruby-friendly

And if I want to change each hash value from fixnum to

string, how would that be done?

this is not working: new_hash = h.each_key {|k| h[k].to_s}

you’re indexing normally, why not use an array?

ar=Array.new(15){|x| 24+x*6}
=> [24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96, 102, 108]
ar[0]
=> 24
ar[5]
=> 54

w added adv that you can refer in groups

ar[0…5]
=> [24, 30, 36, 42, 48, 54]

no need to rush, you can convert to hash anytime :wink:

hh=Hash[*ar.each_with_index.to_a.flatten]
=> {60=>6, 66=>7, 72=>8, 78=>9, 84=>10, 90=>11, 24=>0, 96=>12, 30=>1,
102=>13, 36=>2, 108=>14, 42=>3, 48=>4, 54=>5}


#9

On Wed, Nov 19, 2008 at 10:01 PM, Jason L.
removed_email_address@domain.invalid wrote:

Botp, thanks. That is an easier way to look at it. Can I go back to this
Enumerable#inject? I understand iterating on elements of an array like
this:

range = (1…4)
sum = range.inject(0) {|result, element| result += element }

Don’t use +=, just +
You are injecting the result of the block and not changing the value
of “result” inside the block. That variable is temporary.

hash = [[:diameter, 45], [:id, 2]].inject({}) do |result, element|
result[element.first] = element.last
result
end

You are not iterating over a hash here, you are iterating over the
array [[:diameter, 45], [:id, 2]]

#can someone help me understand better what exactly is happening on each
iteration?
#hash(i=1) = ??
#hash(i=2) = ??

a = 1, 2, 3, 4
#start inject with an initial empty hash
h = a.inject({}) {|s, e| s[e.to_s] = e; s}
#just assigns keys to values, but the keys are strings.

#inject gives you the result of the block (in this case s) on each
iteration as your first inserted object (s) on each go. What you do
with each element and the result of the block on each iteration is up
to you. I wish I could give you a better example, but there are some
experts here that can probably explain it more thoroughly.

Todd


#10

From: Jason L. [mailto:removed_email_address@domain.invalid]

However, iterating over a hash is confusing me. Here is

a simple example:

hash =

[[:diameter, 45], [:id, 2]].inject({}) do |result, element|

result[element.first] = element.last

result

end

can someone help me understand better what exactly is

happening on each

iteration?

hash(i=1) = ??

hash(i=2) = ??

initial:
result = {}

iter 1:
result[:diameter] = 45

iter 2:
result[:id] = 2

i think the confusion stems around the lonely line containing “result”.
You’ll need that because inject will retrieve and use the value of the
code block as the next “result”. (try testing it by removing the
“result” line :slight_smile:

it is in unique cases like these that i find inject not too intuitive :wink:

i might be better off with,

hash = {}
[[:diameter, 45], [:id, 2]].each do |k,v|
hash[k] = v
end

or

hash = Hash[ * [[:diameter, 45], [:id, 2]] . flatten ]


#11

Botp, thanks. That is an easier way to look at it. Can I go back to this
Enumerable#inject? I understand iterating on elements of an array like
this:

range = (1…4)
sum = range.inject(0) {|result, element| result += element }

#I understand inject to be doing this (where my ‘i’ means ‘iteration’):
#sum(i=1) = 0 + 1 = 1
#sum(i=2) = sum(i=1) + 2 = 3
#sum(i=3) = sum(i=2) + 3 = 6
#sum(i=4) = sum(i=3) + 4 = 10
=> sum = 10

#However, iterating over a hash is confusing me. Here is a simple
example:

hash = [[:diameter, 45], [:id, 2]].inject({}) do |result, element|
result[element.first] = element.last
result
end

#can someone help me understand better what exactly is happening on each
iteration?
#hash(i=1) = ??
#hash(i=2) = ??

Thank you!


#12

On 20.11.2008 02:39, Peña wrote:

From: Jason L. [mailto:removed_email_address@domain.invalid]

you’re indexing normally, why not use an array?

I’d even go as far as to question the whole collection approach: since
there is a fairly easy mathematical relationship between key and value
why not just define a function that calculates value from key on demand?

def f(x) 24 + 6 * x end

Kind regards

robert


#13

Written out longhand as above:

Perhaps I should have done this more clearly:

tmp = {}
[[:diameter, 45], [:id, 2]].each do |element|
result = tmp
tmp = (
result[element.first] = element.last
result
)
end
hash = tmp

The part in parentheses is the block, and you can see that the overall
value from executing the block is assigned to ‘tmp’ to be used in the
next iteration.


#14

Jason L. wrote:

range = (1…4)
sum = range.inject(0) {|result, element| result += element }

That should be:

range = (1…4)
sum = range.inject(0) {|result, element| result + element }

To understand this fully, I will write out what inject is doing in
longhand:

range = (1…4)
tmp = 0 # the (0) bit
range.each do |element|
result = tmp
tmp = result + element
#(A)# ######(B)#######
end
sum = tmp # final value of inject

#(B)# is the execution of the block body. #(A)# is done implicitly by
‘inject’: it stores the value calculated by the block, and then passes
this into the next iteration, or else uses it as the final return value.

#However, iterating over a hash is confusing me. Here is a simple
example:

hash = [[:diameter, 45], [:id, 2]].inject({}) do |result, element|
result[element.first] = element.last
result
end

#can someone help me understand better what exactly is happening on each
iteration?

Written out longhand as above:

tmp = {}
[[:diameter, 45], [:id, 2]].each do |element|
result = tmp
result[element.first] = element.last
tmp = result
#(A)# #(B)##
end
hash = tmp

To start with the accumulator is set to an empty hash.

After one iteration, you have done hash[:diameter] = 45, so you’ve added
a new element to the hash. You then give the entire hash object as the
value result from the block, so that it is passed in as ‘result’ to the
next iteration.

On the next iteration, you do hash[:id] = 2, so you’ve added a new value
to it. But the same hash object is the result.

In this case, for every iteration the same hash object is passed in,
and returned so that it can be used by the following iteration. What
you’re doing is modifying that object as a side-effect of the block
executing.

Now, it is possible to get the same result without modifying the hash,
but instead creating a new hash object in each iteration, like this:

hash = [[:diameter, 45], [:id, 2]].inject({}) do |result, element|
result.merge({element.first => element.last})
end

This is what a ‘functional’ programmer would do, where functions cannot
modify data, only create new data. In each iteration you’re merging the
hash built so far with a new one-element hash, to create a new partial
result which is one element larger.

This is less efficient, as you’re repeatedly creating larger and larger
hash objects only to be garbage-collected later. But if you were doing
this in (say) Erlang, that’s what you’d need to do.

Hope this is a bit clearer now…

Brian.


#15

very excellent descriptions. All of this was extremely helpful! Thank
you.