How does shovel ( ) operator work in Ruby Hashes?
When you're doing hash = Hash.new([])
you are creating a Hash whose default value is the exact same Array instance for all keys. So whenever you are accessing a key that doesn't exist, you get back the very same Array.
h = Hash.new([])
h[:foo].object_id # => 12215540
h[:bar].object_id # => 12215540
If you want one array per key, you have to use the block syntax of Hash.new
:
h = Hash.new { |h, k| h[k] = [] }
h[:foo].object_id # => 7791280
h[:bar].object_id # => 7790760
Edit: Also see what Gazler has to say with regard to the #<<
method and on what object you are actually calling it.
Why does the test_default_value_is_the_same_object in about_hashes.rb of Ruby Koans exercises have the answer of an array?
hash = Hash.new([])
Will instantiate a new array with []
(let's call it Harvey), then make a hash with Harvey as its default.
hash[:one]
doesn't exist, so you get Harvey. Harvey gets "uno"
added to him, using the Array#<<
operator (equivalent to harvey.push("one")
)
hash[:two]
also doesn't exist, so you get Harvey again (who, remember, already contains "uno"
). He now also gets "dos"
.
hash[:three]
returns Harvey, still with his "uno"
and "dos"
.
If you wanted the code to behave like you think it should, with a different array in each key, you need to return a new array every time you want a default, not Harvey every single time:
hash = Hash.new { |h, k| h[k] = [] }
And if you just want the hash to have nothing to do with arrays, ignore Harvey, and use Hash#[]=
instead of Array#<<
:
hash = Hash.new()
hash[:one] = "uno"
hash[:two] = "dos"
Working on | | and && operators
Boolean operators in ruby are short-circuiting: if it is possible to determine the value of the expression from the left-hand argument, the right-hand argument isn't evaluated.
Therefore, a simpler mental model for evaluation of a boolean expression involving && is to consider first expressions involving only two operands: the left-hand operand is evaluated first; if the value of this operand is nil or false, the operand is returned and the right-hand operand isn't evaluated; if the left-hand operand is anything else, the right-hand operator is evaluated and its value is returned.
From this definition, it's clear that, as you note, expressions involving boolean operators don't return true or false, but simply a true value or a false value. It's worth noting that this doesn't make any difference in a context where a boolean expression is used only for its true-ness or false-ness.
Hope it will help you.
Array equation explanation
To break anything down line by line one could use REPL:
*a, b = [1, 2, 3, 4]
#⇒ [1, 2, 3, 4]
a
#⇒ [1, 2, 3]
b
#⇒ 4
Using splat operator, we have decomposed the original array to new array and the single value. Now everything is crystal clear: a[b-2]
which is a[2]
, which is in turn 3
(check a
array.) and b is still 4
.
3 + 4
#⇒ 7
Append key/value pair to hash with in Ruby
There is merge!
.
h = {}
h.merge!(key: "bar")
# => {:key=>"bar"}
Ruby's bitwise shift operator confusion in conjuction with array
When you have:
@games << game
<<
is actually a method. If it is a method, you ask, why isn't it written in the normal way:
@games.<<(game)
? You could, in fact, write it that way and it would work fine. Many Ruby methods have names that are symbols. A few others are +
, -
, **
, &
, ||
and %
. Ruby knows you'd prefer writing 2+3
instead of 2.+(3)
, so she let's you do the former (and then quietly converts it to the latter). This accommodation is often referred to as "syntactic sugar".
<<
is one of @games
' methods (and game
is <<
's argument), because @games
is the receiver of the method <<
(technically :<<
). Historically, it's called the "receiver" because with OOP you "send" the method to the receiver.
To say that <<
is a method of @games
means that <<
is an instance method of @games
's class. Thus, we have:
@games.methods.include?(:<<) #=> true
@games.class.instance_methods.include?(:<<) #=> true
I expect @games
is an instance of the class Array
. Array
's instance methods are listed here. For example:
@games = [1,2,3]
@games.class #=> [1,2,3].class => Array
@games << 4 #=> [1,2,3,4]
@games.<<(5) #=> [1,2,3,4,5]
On the other hand, suppose @games
were an instance of Fixnum
. For example:
@games = 7
@games.class #=> 7.class => Fixnum
which in binary looks like this:
@games.to_s(2) #=> "111"
Then:
@games << 2 #=> 28
28.to_s(2) #=> "11100"
because <<
is an instance method of the class Fixnum.
As a third example, suppose @games
were a hash:
@games = { :a => 1 }
@games.class #=> { :a => 1 }.class => Hash
Then:
@games << { :b => 2 }
#=> NoMethodError: undefined method `<<' for {:a=>1}:Hash
The problem is clear from the error message. The class Hash has no instance method <<
.
One last thing: consider the method Object#send. Recall that, at the outset, I said that methods are sent to receivers. Instead of writing:
@games = [1,2,3]
@games << 4 #=> [1,2,3,4]
you could write:
@games.send(:<<, 4) #=> [1, 2, 3, 4]
This is how you should think of methods and receivers. Because send
is an instance method of the class Object
, and all objects inherit Object
's instance methods, we see that send
is "sending" a method (:<<
, which can alternatively be expressed as a string, "<<"
) and its arguments (here just 4
) to the receiver.
There are times, incidentally, when you must use send
to invoke a method. For one, send
works with private
and protected
methods. For another, you can use send
when you want to invoke a method dynamically, where the name of the method is the value of a variable.
In sum, to understand what method
does in:
receiver.method(*args)
look for the doc for the instance method method
in receiver
's class. If you Google "ruby array", for example, the first hit will likely be the docs for the class Array
.
When is a block or object that is passed to Hash.new created or run?
For the benefit of those new to Ruby, I have discussed alternative approaches to the problem, including the one that is the substance of this question.
The task
Suppose you are given an array
arr = [[:dog, "fido"], [:car, "audi"], [:cat, "lucy"], [:dog, "diva"], [:cat, "bo"]]
and wish to to create the hash
{ :dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"] }
First solution
h = {}
arr.each do |k,v|
h[k] = [] unless h.key?(k)
h[k] << v
end
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
This is quite straightforward.
Second solution
More Ruby-like is to write:
h = {}
arr.each { |k,v| (h[k] ||= []) << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
When Ruby sees (h[k] ||= []) << v
the first thing she does is expand it to
(h[k] = h[k] || []) << v
If h
does not have a key k
, h[k] #=> nil
, so the expression becomes
(h[k] = nil || []) << v
which becomes
(h[k] = []) << v
so
h[k] #=> [v]
Note that h[k]
on the left of equality uses the method Hash#[]=, whereas h[k]
on the right employs Hash#[].
This solution requires that none of the hash values equal nil
.
Third solution
A third approach is to give the hash a default value. If a hash h
does not have a key k
, h[k]
returns the default value. There are two types of default values.
Passing the default value as an argument to Hash::new
If an empty array is passed as an argument to Hash::new
, that value becomes the default value:
a = []
a.object_id
#=> 70339916855860
g = Hash.new(a)
#=> {}
g[k]
returns []
when h
does not have a key k
. (The hash is not altered, however.) This construct has important uses, but it is inappropriate here. To see why, suppose we write
x = g[:cat] << "bo"
#=> ["bo"]
y = g[:dog] << "diva"
#=> ["bo", "diva"]
x #=> ["bo", "diva"]
This is because the values of :cat
and :dog
are both set equal to the same object, an empty array. We can see this by examining object_id
s:
x.object_id
#=> 70339916855860
y.object_id
#=> 70339916855860
Giving Hash::new
a block which returns the default value
The second form of default value is to perform a block calculation. If we define the hash with a block:
h = Hash.new { |h,k| h[key] = [] }
then if h
does not have a key k
, h[k]
will be set equal to the value returned by the block, in this case an empty array. Note that the block variable h
is the newly-created empty hash. This allows us to write
h = Hash.new { |h,k| h[k] = [] }
arr.each { |k,v| h[k] << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
As the first element passed to the block is arr.first
, the block variables are assigned values by evaluating
k, v = arr.first
#=> [:dog, "fido"]
k #=> :dog
v #=> "fido"
The block calculation is therefore
h[k] << v
#=> h[:dog] << "fido"
but since h
does not (yet) have a key :dog
, the block is triggered, setting h[k]
equal to []
and then that empty array is appended with "fido", so that
h #=> { :dog=>["fido"] }
Similarly, after the next two elements of arr
are passed to the block we have
h #=> { :dog=>["fido"], :car=>["audi"], :cat=>["lucy"] }
When the next (fourth) element of arr
is passed to the block, we evaluate
h[:dog] << "diva"
but now h
does have a key, so the default does not apply and we end up with
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy"]}
The last element of arr
is processed similarly.
Note that, when using Hash::new with a block, we could write something like this:
h = Hash.new { launch_missiles("any time now") }
in which case h[k]
would be set equal to the return value of launch_missiles
. In other words, anything can be done in the block.
Even more Ruby-like
Lastly, the more Ruby-like way of writing
h = Hash.new { |h,k| h[k] = [] }
arr.each { |k,v| h[k] << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
is to use Enumerable#each_with_object:
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |k,v| h[k] << v }
#=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
which eliminates two lines of code.
Which is best?
Personally, I am indifferent to the second and third solutions. Both are used in practice.
How do you print Ruby hashes as arrays? I keep getting hex values
The issue isn't that you're not using a hash correctly, it's that you are printing out the Game
object. You may need to make your own method to grab the exact attributes you would like printed. For example
def print_assists(game)
puts game.RawStatsDto.assists
end
It is also worth noting that when printing objects for debugging purposes, it is almost always better to use p
instead of puts
e.g. p game
Related Topics
Ruby: File Encryption/Decryption with Private/Public Keys
How to Ensure That Ruby Uses an Openssl Not Vulnerable to Heartbleed
Confusion About Passing Instance Variables to Redirect_To Method. as Seen in Rails Guides
How to Generate Zip File Without Saving to the Disk with Ruby
Rails - Sort by Join Table Data
Rails 3.1 Pipeline - Exclude JavaScript File
Ruby: Dynamically Generate Attribute_Accessor
Looping Through Bits in an Integer, Ruby
Sum the Value of Array in Hash
Add Http(S) to Url If It's Not There
How to Use Bundler with Offline .Gem File