How Does Shovel (<<) Operator Work in Ruby Hashes

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_ids:

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



Leave a reply



Submit