Ruby method Array#<< not updating the array in hash - ruby

Inspired by How can I marshal a hash with arrays? I wonder what's the reason that Array#<< won't work properly in the following code:
h = Hash.new{Array.new}
#=> {}
h[0]
#=> []
h[0] << 'a'
#=> ["a"]
h[0]
#=> [] # why?!
h[0] += ['a']
#=> ["a"]
h[0]
#=> ["a"] # as expected
Does it have to do with the fact that << changes the array in-place, while Array#+ creates a new instance?

If you create a Hash using the block form of Hash.new, the block gets executed every time you try to access an element which doesn't actually exist. So, let's just look at what happens:
h = Hash.new { [] }
h[0] << 'a'
The first thing that gets evaluated here, is the expression
h[0]
What happens when it gets evaluated? Well, the block gets run:
[]
That's not very exciting: the block simply creates an empty array and returns it. It doesn't do anything else. In particular, it doesn't change h in any way: h is still empty.
Next, the message << with one argument 'a' gets sent to the result of h[0] which is the result of the block, which is simply an empty array:
[] << 'a'
What does this do? It adds the element 'a' to an empty array, but since the array doesn't actually get assigned to any variable, it is immediately garbage collected and goes away.
Now, if you evaluate h[0] again:
h[0] # => []
h is still empty, since nothing ever got assigned to it, therefore the key 0 is still non-existent, which means the block gets run again, which means it again returns an empty array (but note that it is a completely new, different empty array now).
h[0] += ['a']
What happens here? First, the operator assign gets desugared to
h[0] = h[0] + ['a']
Now, the h[0] on the right side gets evaluated. And what does it return? We already went over this: h[0] doesn't exist, therefore the block gets run, the block returns an empty array. Again, this is a completely new, third empty array now. This empty array gets sent the message + with the argument ['a'], which causes it to return yet another new array which is the array ['a']. This array then gets assigned to h[0].
Lastly, at this point:
h[0] # => ['a']
Now you have finally actually put something into h[0] so, obviously, you get out what you put in.
So, to answer the question you probably had, why don't you get out what you put in? You didn't put anything in in the first place!
If you actually want to assign to the hash inside the block, you have to, well assign to the hash inside the block:
h = Hash.new {|this_hash, nonexistent_key| this_hash[nonexistent_key] = [] }
h[0] << 'a'
h[0] # => ['a']
It's actually fairly easy to see what is going on in your code example, if you look at the identities of the objects involved. Then you can see that everytime you call h[0], you get a different array.

The problem in your code is that h[0] << 'a' makes an new Array and gives it out when you index with h[0], but doesn't store the modified Array anywhere after the << 'a' because there is no assignment.
Meanwhile h[0] += ['a'] works because it's equivalent to h[0] = h[0] + ['a']. It's the assignment ([]=) that makes the difference.
The first case may seem confusing, but it is useful when you just want to receive some unchanging default element from a Hash when the key is not found. Otherwise you could end up populating the Hash with a great number of unused values just by indexing it.

h = Hash.new{ |a,b| a[b] = Array.new }
h[0] << "hello world"
#=> ["hello world"]
h[0]
#=> ["hello world"]

Related

Ruby Hash Interaction With Pushing Onto Array

So let's say I do the following:
lph = Hash.new([]) #=> {}
lph["passed"] << "LCEOT" #=> ["LCEOT"]
lph #=> {} <-- Expected that to have been {"passed" => ["LCEOT"]}
lph["passed"] #=> ["LCEOT"]
lph["passed"] = lph["passed"] << "HJKL"
lph #=> {"passed"=>["LCEOT", "HJKL"]}
I'm surprised by this. A couple questions:
Why does it not get set until I push the second string on to the array? What is happening in the background?
What is the more idiomatic ruby way to essentially say. I have a hash, a key, and a value I want to to end up in the array associated with the key. How do I push the value in an array associated with a key into a hash the first time. In all future uses of the key, I just want to addd to the array.
Read the Ruby Hash.new documentation carefully - "if this hash is subsequently accessed by a key that doesn’t correspond to a hash entry, the value returned depends on the style of new used to create the hash".
new(obj) → new_hash
...If obj is specified, this single object will be used for all default values.
In your example you attempt to push something onto the value associated with a key which does not exist, so you end up mutating the same anonymous array you used to construct the hash initially.
the_array = []
h = Hash.new(the_array)
h['foo'] << 1 # => [1]
# Since the key 'foo' was not found
# ... the default value (the_array) is returned
# ... and 1 is pushed onto it (hence [1]).
the_array # => [1]
h # {} since the key 'foo' still has no value.
You probably want to use the block form:
new { |hash, key| block } → new_hash
...If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.
For example:
h = Hash.new { |hash, key| hash[key] = [] } # Assign a new array as default for missing keys.
h['foo'] << 1 # => [1]
h['foo'] << 2 # => [1, 2]
h['bar'] << 3 # => [3]
h # => { 'foo' => [1, 2], 'bar' => [3] }
Why does it not get set until I push the second string on to the array?
In short; because you don't set anything in the hash until the point, where you also add the second string to the array.
What is happening in the background?
To see what's happening in the background, let's take this one line at a time:
lph = Hash.new([]) #=> {}
This creates an empty hash, configured to return the [] object whenever a non-existing key is accessed.
lph["passed"] << "LCEOT" #=> ["LCEOT"]
This can be written as
value = lph["passed"] #=> []
value << "LCEOT" #=> ["LCEOT"]
We see that lph["passed"] returns [] as expected, and we then proceed to append "LCEOT" to [].
lph #=> {}
lph is still an empty Hash. At no point have we added anything to the Hash. We have added something to its default value, but that doesn't change lph itself.
lph["passed"] #=> ["LCEOT"]
This is where it gets interesting. Remember above when we did value << ["LCEOT"]. That actually changed the default value that lph returns when a key isn't found. The default value is no longer [], but has become ["LCEOT"]. That new default value is returned here.
lph["passed"] = lph["passed"] << "HJKL"
This is our first change to lph. And what we actually assign to lph["passed"] is the default value (because "passed" is still a non-existing key in lph) with "HJKL" appended. Before this, the default value was ["LCEOT"], after this it is ["LCEOT", "HJKL"].
In other words lph["passed"] << "HJKL" returns ["LCEOT", "HJKL"] which is then assigned to lph["passed"].
What is the more idiomatic Ruby way
Using <<=:
>> lph = Hash.new { [] }
=> {}
>> lph["passed"] <<= "LCEOT"
=> ["LCEOT"]
>> lph
=> {"passed"=>["LCEOT"]}
Also note the change in how the Hash is initialized, using a block instead of a verbatim array. This ensures a new, blank array is created and returned whenever a new key is accessed, as opposed to the same array being used every time.

Complicated ruby inject method

Can't seem to figure this out. Please help me understand what this code is requesting for regarding a variable and what the intended output is supposed to be. Thanks in advance!
def function_name(a)
a.inject({}){ |a,b| a[b] = a[b].to_i + 1; a}.\
reject{ |a,b| b == 1 }.keys
end
Assuming a is an array,
The function first count the occurrences of the keys.
a = ['a', 'b', 'c', 'b']
a.inject({}) { |a,b|
# a: a result hash, this is initially an empty hash (`{}` passed to inject)
# b: each element of the array.
a[b] = a[b].to_i + 1 # Increase count of the item
a # The return value of this block is used as `a` argument of the block
# in the next iteration.
}
# => {"a"=>1, "b"=>2, "c"=>1}
Then, it filter items that occur multiple times:
...reject{ |a,b|
# a: key of the hash entry, b: value of the hash entry (count)
b == 1 # entry that match this condition (occurred only once) is filtered out.
}.keys
# => ["b"]
So, function names like get_duplicated_items should be used instead of function_name to better describe the purpose.
It wants a to be an array, but it doesn't seem to matter what the array is made up of so you'll need some other clue to know what should be in the array.
What the code does is fairly straight foreword. For each item in the array it uses it as a key in a hash. It then basically counts how many times it sees that key. Finally it removes all of the items that only showed up once.
It returns the unique items in the array a that show up 2 or more times.

Ruby hash default value behavior

I'm going through Ruby Koans, and I hit #41 which I believe is this:
def test_default_value_is_the_same_object
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
assert_equal ["uno","dos"], hash[:one]
assert_equal ["uno","dos"], hash[:two]
assert_equal ["uno","dos"], hash[:three]
assert_equal true, hash[:one].object_id == hash[:two].object_id
end
It could not understand the behavior so I Googled it and found Strange ruby behavior when using Hash default value, e.g. Hash.new([]) that answered the question nicely.
So I understand how that works, my question is, why does a default value such as an integer that gets incremented not get changed during use? For example:
puts "Text please: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }
This will take user input and count the number of times each word is used, it works because the default value of 0 is always used.
I have a feeling it has to do with the << operator but I'd love an explanation.
The other answers seem to indicate that the difference in behavior is due to Integers being immutable and Arrays being mutable. But that is misleading. The difference is not that the creator of Ruby decided to make one immutable and the other mutable. The difference is that you, the programmer decided to mutate one but not the other.
The question is not whether Arrays are mutable, the question is whether you mutate it.
You can get both the behaviors you see above, just by using Arrays. Observe:
One default Array with mutation
hsh = Hash.new([])
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => ['one', 'two']
# Because we mutated the default value, nonexistent keys return the changed value
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
One default Array without mutation
hsh = Hash.new([])
hsh[:one] += ['one']
hsh[:two] += ['two']
# This is syntactic sugar for hsh[:two] = hsh[:two] + ['two']
hsh[:nonexistant]
# => []
# We didn't mutate the default value, it is still an empty array
hsh
# => { :one => ['one'], :two => ['two'] }
# This time, we *did* mutate the hash.
A new, different Array every time with mutation
hsh = Hash.new { [] }
# This time, instead of a default *value*, we use a default *block*
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
hsh = Hash.new {|hsh, key| hsh[key] = [] }
# This time, instead of a default *value*, we use a default *block*
# And the block not only *returns* the default value, it also *assigns* it
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => { :one => ['one'], :two => ['two'], :nonexistent => [] }
It is because Array in Ruby is mutable object, so you can change it internal state, but Fixnum isn't mutable. So when you increment value using += internally it get that (assume that i is our reference to Fixnum object):
get object referenced by i
get it internal value (lets name it raw_tmp)
create new object that internal value is raw_tmp + 1
assign reference to created object to i
So as you can see, we created new object, and i reference now to something different than at the beginning.
In the other hand, when we use Array#<< it works that way:
get object referenced by arr
to it's internal state append given element
So as you can see it is much simpler, but it can cause some bugs. One of them you have in your question, another one is thread race when booth are trying simultaneously append 2 or more elements. Sometimes you can end with only some of them and with thrashes in memory, when you use += on arrays too, you will get rid of both of these problems (or at least minimise impact).
From the doc, setting a default value has the following behaviour:
Returns the default value, the value that would be returned by hsh if key did not exist in hsh. See also Hash::new and Hash#default=.
Therefore, every time frequencies[word] is not set, the value for that individual key is set to 0.
The reason for the discrepancy between the two code blocks is that arrays are mutable in Ruby, while integers are not.

Difference between += for Integers/Strings and << For Arrays?

I'm confused about the different results I'm getting when performing simple addition/concatenation on integers, strings and arrays in Ruby. I was under the impression that when assigning variable b to a (see below), and then changing the value of a, that b would remain the same. And it does so in the first two examples. But when I modify Array a in the 3rd example, both a and b are modified.
a = 100
b = a
a+= 5
puts a
puts b
a = 'abcd'
b = a
a += 'e'
puts a
puts b
a = [1,2,3,4]
b = a
a << 5
puts a.inspect
puts b.inspect
The following is what was returned in Terminal for the above code:
Ricks-MacBook-Pro:programs rickthomas$ ruby variablework.rb
105
100
abcde
abcd
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
Ricks-MacBook-Pro:programs rickthomas$
I was given the following explanation by my programming instructor:
Assigning something to a new variable is just giving it an additional label, it doesn't make a copy.
It looks like += is a method, just like <<, and so you'd expect it to behave similarly. But in reality, it's "syntactic sugar", something added to the language to make things easier on developers.
When you run a += 1, Ruby converts that to a = a + 1.
In this case, we're not modifying the Fixnum in a. Instead, we're actually re-assigning on top of it, effectively blowing away the previous value of a.
On the other hand, when you run b << "c", you're modifying the underlying Array by appending the String "c" to it.
My questions are these:
1) He mentions syntactic sugar, but isn't that also what << is, i.e. syntactic sugar for the .push method?
2) Why would it matter if += is syntactic sugar or a more formal method? If there is some difference between the two, then doesn't that mean my previously-understood of syntactic sugar ("syntax within a programming language that is designed to make things easier to read or to express") is incomplete, since this isn't its only purpose?
3) If assigning b to a doesn't make a copy of a, then why doesn't wiping away a's old value mean that b's old value is also wiped away for all 3 cases (Integer, String and Array)?
As you can see, I'm pretty turned around on something that I thought I understood until now. Any help is much appreciated!
You see, names (variable names, like a and b) don't hold any values themselves. They simply point to a value. When you make an assignment
a = 5
then a now points to value 5, regardless of what it pointed to previously. This is important.
a = 'abcd'
b = a
Here both a and b point to the same string. But, when you do this
a += 'e'
It's actually translated to
a = a + 'e'
# a = 'abcd' + 'e'
So, name a is now bound to a new value, while b keeps pointing to "abcd".
a = [1,2,3,4]
b = a
a << 5
There's no assignment here, method << modifies existing array without replacing it. Because there's no replacement, both a and b still point to the same array and one can see the changes made to another.
The answer to 1) and 2) of your question:
The reason why += is syntactic sugar and << is not is fairly simple: += abstracts some of the syntactic expression: a += 1 is just a short version of a = a + 1. << is a method all by itself and is not an alias for push: << can only take one argument, whereas push can take an arbitrary number of arguments: I'm demonstrating this with send here, since [1,2]<<(1,2) is syntactically incorrect:
[1,2].send(:<<, 4, 5) #=> ArgumentError: wrong number of arguments (2 for 1)
push appends all arguments to the array:
[1,2].push(4,5,6) #=> [1,2,4,5,6]
Therefore, << is an irreplaceable part of the ruby array, since there is no equivalent method. One could argue that it is some kind of syntactic sugar for push, with disregard for the differences shown above, since it makes most operations involving appending elements to an array simpler and syntactically more recognizable.
If we go deeper and have a look at the different uses of << throughout ruby:
Push An Element to an array:
[1,2] << 5
concatenate a string to another, here, << is actually an alias for concat
"hello " << "world"
Open up the singleton class and define a method on a class:
class Foo
class << self
def bar
puts 'baz'
end
end
end
And last but not least append self to self in Integers:
1 << 2 #translates to ((1 + 1) + (1 + 1))
We can see that << actually stands for append throughout ruby, since it always appears in a context where something is appended to something already existing. I would therefore rather argue that << is a significant part of the ruby syntax and not syntactic sugar.
And the answer to 3)
The reason why b's assignment is not modified (or wiped of its old value, as you put it) if you use the += operator is just that a += 1, as a short for a = a + 1, reassigns a's value and therefore assigns a new object along with that. << is modifying the original object. You can easily see this using the object_id:
a = 1
b = a
b.object_id == a.object_id #=> true
a += 1
b.object_id == a.object_id #=> false
a = [1,2]
b = a
b.object_id == a.object_id #=> true
a << 3
b.object_id == a.object_id #=> true
There are also some caveats to Integer instances (100, 101) and so on: the same number is always the same object, since it does not make any sense to have multiple instances of, for example 100:
a = 100
b = a
b.object_id == a.object_id #=> true
a += 1
b.object_id == a.object_id #=> false
a -= 1
b.object_id == a.object_id #=> true
This also shows that the value, or the Integer instance (100) is just assigned to the variable, so the variable itself is not an object, it just points to it.
String#+ :: str + other_str → new_str Concatenation—Returns a new String containing other_str concatenated to str.
String#<< :: str << integer → str : Append—Concatenates the given object to str.
<< doesn't create the new object, where as + does.
Sample1:
a = 100
p a.object_id
b = a
p b.object_id
a+= 5
p a.object_id
p b.object_id
puts a
puts b
Output:
201
201
211
201
105
100
Your example:
a = 100
b = a
a+= 5
is equivalent to:
a = 100
b = a
a = 100 + 5
Afterwards a holds a reference to 105 and b still holds a reference to 100. This is how assignment works in Ruby.
You expected += to change the object instance 100. In Ruby, however (quoting the docs):
There is effectively only one Fixnum object instance for any given integer value
So there's only one object instance for 100 and another (but always the same) one for 105. Changing 100 to 105 would change all 100's to 105. Therefore, it is not possible to modify these instances in Ruby, they are fixed.
A String instance on the other hand can be modified and unlike Integer there can be multiple instances for the same sequence of bytes:
a = "abcd"
b = "abcd"
a.equal? b # returns true only if a and b are the same object
# => false
a << "e" concatenates "e" to a, thus changing the receiver: a is still referencing the same object instance.
Other methods like a += "e" return (and assign) a new String: a would reference this new instance afterwards.
The documentation is pretty clear:
str + other_str → new_str
Concatenation—Returns a new String containing other_str concatenated to str.
str << obj → str
Append—Concatenates the given object to str.
I can answer your questions.
1) No, the << method is not syntactic sugar for push. They are both methods with different names. You can have objects in Ruby that define one but not the other (for example String).
2) For a normal method like <<, the only thing that can happen as a result of a << x is that the object that a is pointing to gets modified. The statements a << x or a.push(x) cannot create a new object and change the variable a to point at it. That's just how Ruby works. This kind of thing is called "calling a method".
The reason that += being syntactic sugar matters is that means it can be used to modify a variable without mutating the old object that the variable used to point to. Consider a += x. That statement can modify what object a is pointing to because it is syntactic sugar for an actual assignment to a:
a = a + x
There are two things happening above. First the + method is called on a with one argument of x. Then the return value of the + method, whatever it is, is assigned to the variable a.
3) The reason that your Array case is different is because you chose to mutate the array instead of creating a new array. You could have used += to avoid mutating the array. I think that these six examples that will clear things up for you and show you what is possible in Ruby:
Strings without mutations
a = "xy"
b = a
a += "z"
p a # => "xyz"
p b # => "xy"
Strings with mutations
a = "xy"
b = a
a << "z"
p a # => "xyz"
p b # => "xyz"
Arrays without mutations
a = [1, 2, 3]
b = a
a += [4]
p a # => [1, 2, 3, 4]
p b # => [1, 2, 3]
Arrays with mutations
a = [1, 2, 3]
b = a
a.concat [4]
p a # => [1, 2, 3, 4]
p b # => [1, 2, 3, 4]
Integers without mutations
a = 100
b = a
a += 5
puts a # => 105
puts b # => 100
Integers with mutations
Mutating an integer is actually not possible in Ruby. Writing a = b = 89 actually does create two copies of the number 89, and the number cannot be mutated ever. Only a few, special types of objects behave like this.
Conclusion
You should think of a variable as just a name, and an object as a nameless piece of data.
All objects in Ruby can be used in an immutable way where you never actually modify the contents of an object. If you do it that way, then you don't have to worry about the b variable in our examples changing on its own; b will always point to the same object and that object will never change. The variable b will only change when you do some form of b = x.
Most objects in Ruby can be mutated. If you have several variables referring to the same object and you choose to mutate the object (e.g. by calling push), then that change will affect all the variables that are pointing to the object. You cannot mutate Symbols and Integers.
I guess the above answers explain the reason. Note also that if you want to ensure b is no pointer, you can use b = a.dup instead of b=a (dup for duplicate )
I'll try and answer your question to the best of my ability.
Yes, both are "sugars" but they work differently and as Sergio Tulentsev said, << it's not really a sugar but it's an alias.
And those works as an alias in Unix like languages, it's a shorter shorthand for something named after your liking.
So for the first scenario: += basically what's happening is that you're saying:
for the value 100 assign label 'a'.
for label 'b' assign the value of label 'a'.
for label 'a' take the value of label 'a' and add 5 to label 'a's value and return a new value
print label 'a' #this now holds the value 105
print label 'b' #this now holds the value 100
Under the hood of Ruby this has to do with the += returning a new String when that happens.
For the second scenario: << it's saying:
for value [1,2,3,4] assign label 'a'
for label 'b' assign the value of label 'a'
for label 'a' do the '<<' thing on the value of label 'a'.
print label 'a'
print label 'b'
And if you're applying the << to a string it will modify the existing object and append to it.
So what's different. Well the difference is that the << sugar doesn't act like this:
a is the new value of a + 5
it acts like this:
5 into the value of 'a'
2) Because the way you use the syntactic sugar in this case is making it easier for the
developer to read and understand the code. It's a shorthand.
Well, shorthands, if you call them that instead, do serve diffrent purposes.
The syntactic sugar isn't homogenous ie. it doesn't work the same way for all "sugars".
3) On wiping values:
It's like this.
put value 100 into the label 'a'
put the value of label 'a' into label 'b'
remove label 'a' from the value.
So
a = 100
b = a
a = nil
puts a
puts b
=> 100
Variables in Ruby doesn't hold values they point to values!

Ruby 'tap' method - inside assignment

Recently I discovered that tap can be used in order to "drily" assign values to new variables; for example, for creating and filling an array, like this:
array = [].tap { |ary| ary << 5 if something }
This code will push 5 into array if something is truthy; otherwise, array will remain empty.
But I don't understand why after executing this code:
array = [].tap { |ary| ary += [5] if something }
array remains empty. Can anyone help me?
In the first case array and ary point to the same object. You then mutate that object using the << method. The object that both array and ary point to is now changed.
In the second case array and ary again both point to the same array. You now reassign the ary variable, so that ary now points to a new array. Reassigning ary however has no effect on array. In ruby reassigning a variable never effects other variables, even if they pointed to the same object before the reassignment.
In other words array is still empty for the same reason that x won't be 42 in the following example:
x = 23
y = x
y = 42 # Changes y, but not x
Edit: To append one array to another in-place you can use the concat method, which should also be faster than using +=.
I want to expand on this a bit:
array = [].tap { |ary| ary << 5 if something }
What this does (assuming something is true-ish):
assigns array to [], an empty array.
array.object_id = 2152428060
passes [] to the block as ary. ary and array are pointing to the same array object.
array.object_id = 2152428060
ary.object_id = 2152428060
ary << 5 << is a mutative method, meaning it will modify the receiving object. It is similar to the idiom of appending ! to a method call, meaning "modify this in place!", like in .map vs .map! (though the bang does not hold any intrinsic meaning on its own in a method name). ary has 5 inserted, so ary = array = [5]
array.object_id = 2152428060
ary.object_id = 2152428060
We end with array being equal to [5]
In the second example:
array = [].tap{ |ary| ary += [5] if something }
same
same
ary += 5 += is short for ary = ary + 5, so it is first modification (+) and then assignment (=), in that order. It gives the appearance of modifying an object in place, but it actually does not. It creates an entirely new object.
array.object_id = 2152428060
ary.object_id = 2152322420
So we end with array as the original object, an empty array with object_id=2152428060 , and ary, an array with one item containing 5 with object_id = 2152322420. Nothing happens to ary after this. It is uninvolved with the original assignment of array, that has already happened. Tap executes the block after array has been assigned.

Resources