Brandon
Brandon

Reputation: 1751

Convert Ruby Array into Hash by Looping Through

I am trying to convert an array into a hash by iterating through it. I have the following array:

weekdays = [["Monday",2],["Tuesday",4],["Thursday",5]]

I'm looping through the array to create a hash like so:

hash_weekdays = Hash.new
weekdays.each do |item|
  hash_weekdays["weekday"] = item[0]
  hash_weekdays["number"] = item[1]
end

This, however, only shows me the last weekday. Any ideas on where to go from here? Thanks!

Upvotes: 0

Views: 856

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110755

You need to change your code to the following:

hash_weekdays = Hash.new
weekdays.each do |item|
  hash_weekdays[item[0]] = item[1]
end
hash_weekdays
 #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5} 

When the first element of weekdays (["Monday",2]) is passed to the block, the block variable is assigned its value:

item = ["Monday",2]

Since you need to reference each element of item, it's common to use two block variables whose values are assigned using parallel assignment (aka multiple assignment):

day, nbr = ["Monday",2]
    #=> ["Monday", 2] 
day #=> "Monday" 
nbr #=> 2 

This allows you to write

hash_weekdays = {} # the more common way of writing hash_weekdays = Hash.new
weekdays.each { |day, nbr| hash_weekdays[day] = nbr } # used {...} rather than do..end
hash_weekdays

which is arguably clearer.

Notice that you first initialize hash_weekdays to an empty hash, then need the line hash_weekdays at the end if you wish to obtain the new value of the hash (as the last line of a method, for example). You can reduce this to a single line (very Ruby-like) by using the method Enumerable#each_with_object:

weekdays.each_with_object({}) { |item, hash_weekdays| hash_weekdays[item[0]] = item[1] }
  #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5} 

Notice this uses parallel assignment. The first element passed to the block, [["Monday", 2], {}], is assigned as follows:

item, hash_weekdays = weekdays.each_with_object({}).next
  #=> [["Monday", 2], {}]
item
  #=> ["Monday", 2] 
hash_weekdays
  #=> {} 

The Ruby way is to use parallel assignment in a slightly more complex way:

weekdays.each_with_object({}) { |(day, nbr), hash_weekdays|
  hash_weekdays[day] = nbr }
  #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5} 

As others have noted, the most straightforward answer is to use the method Hash::[] or (introduced in v2.0) Array#to_h:

Hash[weekdays]
  #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5}

Hash[*weekdays.flatten] #=> Hash["Monday", 2, "Tuesday", 4, "Thursday", 5] 
  #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5}

weekdays.to_h
  #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5}

Upvotes: 1

guitarman
guitarman

Reputation: 3320

A hash holds key-value pairs. Each key references one value only and each key is uniq in a hash. The values of your keys weekday and number will be overwritten with each iteration. That's the reason why you get the result of the last iteration only.

You could convert it to an array of hashes as an alternative.

weekdays.map{|day, number| {weekday:day, number: number}}
# => [{:weekday=>"Monday", :number=>2}, {:weekday=>"Tuesday", :number=>4}, {:weekday=>"Thursday", :number=>5}]

If your weekdays or numbers are unique in your main array you could use one kind of them as key and the other as value in a hash.

weekdays.to_h
# => {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5}

or

weekdays.to_h.invert
# => {2=>"Monday", 4=>"Tuesday", 5=>"Thursday"}

Upvotes: 1

Sagar Pandya
Sagar Pandya

Reputation: 9508

You could do this instead:

weekdays.to_h #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5}

or fixing your code:

weekdays = [["Monday",2],["Tuesday",4],["Thursday",5]]

hash_weekdays = Hash.new
weekdays.each do |item|
  hash_weekdays[item[0]] = item[1]
end

p hash_weekdays #=> {"Monday"=>2, "Tuesday"=>4, "Thursday"=>5}

Upvotes: 1

Related Questions