Forum: Ruby build hash by iterating

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.
A74096209c78118ff69d50a51129d697?d=identicon&s=25 Jason Lillywhite (jlillywh)
on 2008-11-19 16:34
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!
1bac2e65d64faf472cf2ebc94f0f5ee0?d=identicon&s=25 Ara Howard (ahoward)
on 2008-11-19 16:55
(Received via mailing list)
On Nov 19, 2008, at 8:31 AM, Jason Lillywhite 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/
53581739a445ad78250a676dabddf55f?d=identicon&s=25 James Coglan (Guest)
on 2008-11-19 16:56
(Received via mailing list)
>
> 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 }
87349a7a95b3f2e83c20194ef122885c?d=identicon&s=25 Einar Magnús Boson (Guest)
on 2008-11-19 17:11
(Received via mailing list)
On 19.11.2008, at 15:31 , Jason Lillywhite 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
A74096209c78118ff69d50a51129d697?d=identicon&s=25 Jason Lillywhite (jlillywh)
on 2008-11-19 17:11
perfect. Thank you.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-11-19 17:16
> 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.
A74096209c78118ff69d50a51129d697?d=identicon&s=25 Jason Lillywhite (jlillywh)
on 2008-11-19 17:56
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
6087a044557d6b59ab52e7dd20f94da8?d=identicon&s=25 Peña, Botp (Guest)
on 2008-11-20 02:44
(Received via mailing list)
From: Jason Lillywhite [mailto:jason.lillywhite@gmail.com]
# 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 ;)

> 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}
A74096209c78118ff69d50a51129d697?d=identicon&s=25 Jason Lillywhite (jlillywh)
on 2008-11-20 05:05
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!
289cf19aa581c445915c072bf45c5e25?d=identicon&s=25 Todd Benson (Guest)
on 2008-11-20 05:45
(Received via mailing list)
On Wed, Nov 19, 2008 at 10:01 PM, Jason Lillywhite
<jason.lillywhite@gmail.com> 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
6087a044557d6b59ab52e7dd20f94da8?d=identicon&s=25 Peña, Botp (Guest)
on 2008-11-20 05:48
(Received via mailing list)
From: Jason Lillywhite [mailto:jason.lillywhite@gmail.com]
# 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 :)

it is in unique cases like these that i find inject not too intuitive ;)

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 ]
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2008-11-20 08:42
(Received via mailing list)
On 20.11.2008 02:39, Peña wrote:
> From: Jason Lillywhite [mailto:jason.lillywhite@gmail.com]

> 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
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-11-20 13:14
Jason Lillywhite 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.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-11-20 13:17
> 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.
A74096209c78118ff69d50a51129d697?d=identicon&s=25 Jason Lillywhite (jlillywh)
on 2008-11-20 14:28
very excellent descriptions. All of this was extremely helpful! Thank
you.
This topic is locked and can not be replied to.