RubyKoans - Confusing hash example [duplicate] - ruby

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How does shovel (<<) operator work in Ruby Hashes?
I'm making my way through the RubyKoans, and one of the examples regarding hashes is really confusing me. The example is in the about_hashes.rb file. Here is the code that is confusing me:
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
assert_equal __, hash[:one]
For those unfamiliar with RubyKoans, you are supposed to fill in the correct value for the double-underscore.
In this example, I see that the variable named hash is using the Hash object constructor, which replaces the default value of nil with an empty Array. Then, two items are added to hash, each with one array element, using the << Ruby operator. I would expect that hash[:one] would return an array of value ["uno"], but Ruby is telling me that it actually is ["uno", "dos"]. What gives?

The Hash constructor is using a reference to the same array as the default value, so all hash values will actually be initialized to the same array by default until you use the []= operator on a hash key (e.g., hash[:one] = some_new_object). This is why you see both strings in the array.
According to this answer to another question, you need to use a block form for the Hash constructor. That block will be executed for every new hash key:
hash = Hash.new { |h,k| h[k] = [] }
As for the colon-prefixed tokens, those are literals for what are called symbols. Symbols are much like strings, but have a few crucial differences. One is that the same symbol value will always reference the same object in memory (which is not necessarily true of strings). Symbols have other characteristics that make them perform slightly better than constant strings. Symbols are otherwise very much like string literals.

Related

Unexpected FrozenError when appending elements via <<

I have a hash containing names and categories:
hash = {
'Dog' => 'Fauna',
'Rose' => 'Flora',
'Cat' => 'Fauna'
}
and I want to reorganize it so that the names are grouped by their corresponding category:
{
'Fauna' => ['Dog', 'Cat'],
'Flora' => ['Rose']
}
I am adding each names via <<:
new_hash = Hash.new
hash.each do |name , category|
if new_hash.key?(category)
new_file[category] << name
else
new_hash[category] = name
end
end
But I am being told that this operation is being performed on a frozen element:
`<<' : Can’t modify frozen string (FrozenError)
I suppose this is because each yields frozen objects. How can I restructure this code so the '.each' doesn't provide frozen variables?
I needed to add the first name to an array and then that array to the hash.
new_hash = Hash.new
hash.each do |name , category|
if new_hash.key?(category)
new_file[category] << name
else
new_hash[category] = [name] # <- must be an array
end
end
How can I restructure this code so the '.each' doesn't provide frozen variables?
Short answer: you can't.
Hash#each doesn't "provide frozen variables".
First off, there is no such thing as a "frozen variable". Variables aren't frozen. Objects are. The distinction between variables and objects is fundamental, not just in Ruby but in any programming language (and in fact pretty much everywhere else, too). If I have a sticker with the name "Seamus" on it, then this sticker is not you. It is simply a label that refers to you.
Secondly, Hash#each doesn't provide "variables". In fact, it doesn't provide anything that is not in the hash already. It simply yields the objects that are already in the hash.
Note that, in order to avoid confusion and bugs, strings are automatically frozen when used as keys. So, you can't modify string keys. You can either make sure they are correct from the beginning, or you can create a new hash with new string keys. (You can also add the new keys to the existing hash and delete the old keys, but that is a lot of complexity for little gain.)

How to compare a Hash Key with an Integer in rails

params[:mg_question_id].each do |question|
#poll=MgPollData.new
#poll.mg_question_id= question
params[:options_id].each do |k,v|
if k==question
#poll.answer= v
end
end
#poll_answers.save
end
here, I'm comparing the array value with the hash key. But hash key is string and array is integer - that's why I'm unable to compare the both.
Is there any solution regarding this?
You want to call :to_i on question.
params[:mg_question_id].each do |question_id|
#poll=MgPollData.build(:mg_question_id => question_id.to_i)
params[:options_id].each do |k,v|
if k.to_i==question_id.to_i
#poll.answer= v
end
end
#poll_answers.save
end
As others suggest you can call .to_i on question
params[:mg_question_id].each do |question_id|
#poll=MgPollData.build(:mg_question_id => question_id.to_i)
params[:options_id].each do |k,v|
if k.to_i==question_id.to_i
#poll.answer= v
end
end
#poll_answers.save
However, it's important to remember that arrays are index based beginning with 0. While hashes are object based, and hash keys are often symbols. key: "Value", and if the key is a symbol eg, :key calling .to_i may throw an error or in earlier ruby versions have unexpected side effects such as:
Returns an integer that is unique for each symbol within a particular execution of a program. Similar to object_id
:fred.to_i #=> 9809
For that reason, I might call .to_s rather than .to_i opting to have them both be strings and avoid the problems with converting symbols to integers.
Hope this helps some...

Ruby modify hash in "each" block. Pass-by-value subtlety ? [duplicate]

This question already has an answer here:
Ruby - Parameters by reference or by value? [duplicate]
(1 answer)
Closed 8 years ago.
According to this question, ruby is strictly pass-by-value. I came across a case though, which consists of modifying a hash like so:
h = {"one" => ["un", "ein"], "two"=> ["deux", "zwei"]}
h.each { |k,v| v << "overriden"}
resulting in:
{"one"=>["un", "ein", "overriden"], "two"=>["deux", "zwei", "overriden"]}
However, the following behaves differently:
h = {"one" => "un", "two"=> "deux"}
h.each { |k,v| v = "overriden"}
resulting in:
{"one"=>"un", "two"=>"deux"}
How could I have predicted this?
If you read the next paragraph in the answer you linked, it says this:
Ruby doesn't have any concept of a pure, non-reference value, so you certainly can't pass one to a method. Variables are always references to objects. In order to get an object that won't change out from under you, you need to dup or clone the object you're passed, thus giving an object that nobody else has a reference to.
The << operator modifies an array in place. You have a reference to the array, the hash has a reference to that array, and when the array changes, they both point at the same array.
When you use = you are assigning a value to a variable. In a way, you are telling the variable to refer to something else, instead of doing something to the thing the variable references.

How to save an array of information coming from a hash in Ruby

I am new to ruby and don't have much experience with hashes, I have a variable named tweets and it is a hash as such:
{"statuses"=>[{"metadata"=>{"result_type"=>"recent", "iso_language_code"=>"tl"}, "lang"=>"tl"}]}
I would like to save the array of information as a separate variable in an array. How would I go about this?
Hash's have 2 very nice methods,
hash.values
hash.keys
in your case -
h = {"statuses"=>[{"metadata"=>{"result_type"=>"recent", "iso_language_code"=>"tl"}, "lang"=>"tl"}]}
p h.values
p.keys
These output arrays of each type. This might be what you want.
Also, this question will very well be closed. 1 Google search reported several Hash to Array SO questions.
Ruby Hash to array of values
Converting Ruby hashes to arrays
If you have a Hash like so:
hash = {:numbers => [1,2,3,4]}
And you need to capture the array into a new variable. You can just access the key and assign it to a new variable like so:
one_to_five = hash[:numbers]
However, note that the new variable actually holds the array that is in the hash. So altering the hash's array alters the new variable's array.
hash[:numbers] << 6
puts one_to_five #=> [1,2,3,4,5,6]
If you use dup, it will create a copy of the array so it will be two separate arrays.
one_to_five = hash[:numbers].dup
hash[:numbers] << 6
puts one_to_five #=> [1,2,3,4,5]
So, in your case:
hash = {'statuses' => [{"metadata"=>{"result_type"=>"recent", "iso_language_code"=>"tl"}, "lang"=>"tl"}]}
new_array = hash['statuses'].dup
However, it would be interesting to see what it is you are wishing to accomplish with your code, or at least get a little more context, because this may not be the best approach for your final goal. There are a great many things you can do with Arrays and Hashes (and Enumerable) and I would encourage you to read through the documentation on them.

Can someone explain to me in detail what is going on in this Ruby block?

Learning Ruby. Needed to create a hash of arrays. This works... but I don't quite understand what Ruby is doing. Could someone explain it in detail?
months = Hash.new{|h, k| h[k] = []}
This uses the Hash.new constructor to create a hash whose items default to the empty list. You can therefore do something like this:
months[2012] << 'January'
and the array will be created without you doing a months[2012] = [] first.
Short explanation: { |h, k| h[k] = [] } Is a Ruby block and as mu has mentioned, it can to some degree be compared to function in Javascript or lambda in Python. It basically is an anonymous piece of code that takes two arguments (h, k, the pipes only have the meaning to separate parameters from code) and returns a value. In this case it takes a hash and a key and sets the value of the key to a new array []. It returns a reference to the hash (but that's not important in this context).
Now to Hash.new: It is the constructor for a Hash, of which I assume you already now what it is. It optionally takes a block as an argument that is called whenever a key is accessed that does not yet exist in the Hash. In the above example, the key 2012 was not accessed before, so the block is called, which creates a new array and assigns it to the key. Afterwards, the << operator can work with that array instance.

Resources