Reassigned hash changes the original hash - ruby

Why is variable a getting changed and how do I prevent it?
a = [] # => []
b = a # => []
b << :hello # => [:hello]
p a # => [:hello]
# >> [:hello]
I see the responds to use clone, and wondering why the below works and in which situations .clone is needed and not needed
a = "string" # => "string"
b =a # => "string"
b = "changed" # => "changed"
a # => "string"

Why is variable a getting changed and how do I prevent it?
a = [] # => []
b = a # => []
b << :hello # => [:hello]
p a # => [:hello]
# >> [:hello]
The variable a is not getting changed. The only way a variable can be changed is by assigning to it (ignoring reflection like Binding#local_variable_set), which you are not doing. Therefore, a doesn't change.
The object that is referenced by both a and b gets changed. But changing the object and changing the variable are two completely different things.
I see the responds to use clone, and wondering why the below works and in which situations .clone is needed and not needed
a = "string" # => "string"
b =a # => "string"
b = "changed" # => "changed"
a # => "string"
This works because you never change the object. You change the variable.

Why do you use a mutating method for the array and rebinding for the string and still expect they to behave similarly?
a = "string" #⇒ "string"
b = a #⇒ "string"
b << "changed" #⇒ "stringchanged"
a #⇒ "stringchanged"

As I understand its because of the memory usage.
When you initialize object, Ruby will first initialize object in the memory. Then, the variable points to that memory address. When you assign this variable to another one, that one will point to that address also
For example,
a = []
a.object_id # 70220203482480
b = a
b.object_id # 70220203482480
When you add new element, it means, you add the value to the array which initialized in memory, calling a and b will both show that array with new element.
a.push(1)
b # [1]
Let see the second example
c = 'reference'
d = c
c.object_id #70220203442960
d.object_id #70220203442960
c.capitalize! # 'Reference'
d # 'Reference'
If you assign d = 'new object', Ruby will create another object in the memory and give it value as string new object, and then, d will point to that new memory address
d = 'new object'
d.object_id # 70220203334840 (different one)
c # 'Reference' (cause c still point to the last object in memory)

Related

There's some way to reuse string?

In case,
A = "A"
B = "#{A}"
It's B = "A", right?
And now I would like to change (A = "C") and want B to change by effect of A too.
Is there some way to do that?
Let's talk about naming conventions first. Uppercase identifiers are used for constants in Ruby. Per default assigning a new value to an already initialized constant raises a warning in Ruby:
A = 'B'
A = 'C'
#=> warning: already initialized constant A
#=> warning: previous definition of A was here
Therefore I will use normal instance variables and reader methods in the following example. As Pascal Betz already pointed out: If you want b to depend on the current value of a then b should be a method:
def b
#a
end
#a = 'A'
b
#=> "A"
#a = 'C'
b
#=> "C"
If you do this:
a = "A"
b = "#{a}"
a and b are strings with the same content, but they're not the same objects:
b == a
# => true
b.equal? a
# => false
a.object_id
# => 24494240
b.object_id
# => 24679880
Ruby strings are mutable. So if b and a refer to the same string, modifying a will automatically update b too:
a = "A"
# => "A"
b = a
# => "A"
a.replace 'C'
# => "C"
b
# => "C"
It works in both directions:
b.gsub!('C', 'D')
# => "D"
a
# => "D"

Why does updating a hash set to a variable update that variable?

I can update a hash set to another variable like this:
d = a = {b: 'c'}
a[:b] = 'qwerty'
d # => {:b=>'qwerty'} # What is this magic?
d[:b] = 'blah'
a # => {:b=>'blah'} # And this magic?
Why does this not happen when using primitive variables?
d = a = 'b'
a = 'c'
d # =>'b' # Why is it still the same?
You're doing two very different things.
Your "primitive variable" example:
a = 'b'
d = a
a = 'c'
is a simple assignment so that at the end of line 2, a and d refer to the same object. Then on line 3, a is assigned a new object and a and d end up referring to different objects.
Your first example:
a = {b: 'c'}
d = a
a[:b] = 'qwerty'
is an assignment followed by a call to a method that mutates its receiver. The third line could also be written as one of the following that make the the method call more explicit:
a.[]=(:b, 'qwerty')
a.send(:[]=, :b, 'qwerty')
So you're comparing an assignment by a mutating method call with a series of assignments.
If your second example used a mutator method on a:
a = 'b'
# => 'b'
d = a
# => 'b'
a[0] = 'c' # String#[]= alters its receiver
# => 'c'
d
# => 'c'
then both a and d would change because you're not doing an assignment to change the object that a refers to, you're changing the referred object.
This question isn't quite a duplicate of Is Ruby pass by reference or by value?, close but not quite. I'm sure this is a duplicate of several other questions but I can't find them right now. If someone finds a proper duplicate, let me know and I'll delete this and pull out my dupe-hammer.
Unlike for primitives both variables point to the same underlying object. If you need a copy you can use clone or dup.

Changing a Hash in ruby using an enumerator

Here is my example program:
what = {:banana=>:fruit, :pear=>:fruit, :sandal=>:fruit, :panda=>:fruit, :apple=>:fruit}
what.map do |w|
p "is this right?"
awesome_print w
fix = gets
fix.chop!
if (fix == "N")
p "Tell me what it should be"
correction = gets
w[1] = correction.chop!.to_sym
end
p w
end
I run it, and I get this (my input included):
"is this right?"
[
[0] :banana,
[1] :fruit
]
Y
[:banana, :fruit]
"is this right?"
[
[0] :pear,
[1] :fruit
]
Y
[:pear, :fruit]
"is this right?"
[
[0] :sandal,
[1] :fruit
]
N
"Tell me what it should be"
footwear
[:sandal, :footwear]
"is this right?"
[
[0] :panda,
[1] :fruit
]
N
"Tell me what it should be"
animal
[:panda, :animal]
"is this right?"
[
[0] :apple,
[1] :fruit
]
Y
[:apple, :fruit]
=> [[:banana, :fruit], [:pear, :fruit], [:sandal, :footwear], [:panda, :animal], [:apple, :fruit]]
>> what
=> {:banana=>:fruit, :pear=>:fruit, :sandal=>:fruit, :panda=>:fruit, :apple=>:fruit}
My question is how can I change the Hash? irb tells me when I run the program that each enumerated element is processed, but the results aren't saved in my hash what.
If you want to mutate the hash in place (as you seem to want), simply do this:
my_hash.each do |key,value| # map would work just as well, but not needed
my_hash[key] = some_new_value
end
If you want to create a new hash, without changing the original:
new_hash = Hash[ my_hash.map do |key,value|
[ key, new_value ]
end ]
The way this works is that Enumerable#map returns an array (in this case an array of two-element key/value pairs), and Hash.[] can turn [ [a,b], [c,d] ] into { a=>b, c=>d }.
What you were doing—hash.map{ … }—was mapping each key/value pair to a new value and creating an array…and then doing nothing with that array. While there is Array#map! which will destructively mutate an array in place, there is no equivalent Hash#map! to destructively mutate a hash in a single step.
Note also that if you want to destructively mutate a Hash—or any other object that references other mutable objects—in place you can just destructively mutate those objects during normal iteration:
# A simple hash with mutable strings as values (not symbols)
h = { a:"zeroth", b:"first", c:"second", d:"third" }
# Mutate each string value
h.each.with_index{ |(char,str),index| str[0..-3] = index.to_s }
p h #=> {:a=>"0th", :b=>"1st", :c=>"2nd", :d=>"3rd"}
However, since you are using symbols for the values in your sample code—and since symbols are not mutable—this final note does not directly apply there.
Instead of:
w[1] = correction.chop!.to_sym
Try assigning to the hash directly:
what[w[0]] = correction.chop!.to_sym
Ruby is creating that w array just to pass you the key and value. Assigning to that array isn't going to change your hash; it's only changing that temporary array.

The confusing Ruby method returns value

I have Ruby code:
def test_111(hash)
n = nil
3.times do |c|
if n
n[c] = c
else
n = hash
end
end
end
a = {}
test_111(a)
p a
Why it print {1=>1, 2=>2}, not the {} ??
In the test_111 method, the hash and the a use the same memory?
How can the a value be changed in the test_111 method?
I can't understand
Hashes are passed by reference. So, when you change a method parameter (which is a Hash), you change the original hash.
To avoid this, you should clone the hash.
test_111(a.dup)
This will create a shallow copy (that is, it will not clone child hashes that you may have).
A little illustration of what shallow copy is:
def mutate hash
hash[:new] = 1
hash[:existing][:value] = 2
hash
end
h = {existing: {value: 1}}
mutate h # => {:existing=>{:value=>2}, :new=>1}
# new member added, existing member changed
h # => {:existing=>{:value=>2}, :new=>1}
h = {existing: {value: 1}}
mutate h.dup # => {:existing=>{:value=>2}, :new=>1}
# existing member changed, no new members
h # => {:existing=>{:value=>2}}
In ruby, just about every object is passed by reference. This means when you do something as simple as
a = b
unless a was one of the simple types, after this assignment a and b will point to the same thing.
This means if you alter the second variable, the first is affected the same way:
irb(main):001:0> x = "a string"
=> "a string"
irb(main):002:0> y = x
=> "a string"
irb(main):003:0> x[1,0] = "nother"
=> "nother"
irb(main):004:0> x
=> "another string"
irb(main):005:0> y
=> "another string"
irb(main):006:0>
and of course the same applies for hashes:
irb(main):006:0> a = { :a => 1 }
=> {:a=>1}
irb(main):007:0> b = a
=> {:a=>1}
irb(main):008:0> a[:b] = 2
=> 2
irb(main):009:0> a
=> {:a=>1, :b=>2}
irb(main):010:0> b
=> {:a=>1, :b=>2}
irb(main):011:0>
If you don't want this to happen, use .dup or .clone:
irb(main):001:0> a = "a string"
=> "a string"
irb(main):002:0> b = a.dup
=> "a string"
irb(main):003:0> a[1,0] = "nother"
=> "nother"
irb(main):004:0> a
=> "another string"
irb(main):005:0> b
=> "a string"
irb(main):006:0>
For most people dup and clone have the same effect.
So if you write a function that modifies one of its parameters, unless you specifically want those changes to be seen by the code that calls the function, you should first dup the parameter being modified:
def test_111(hash)
hash = hash.dup
# etc
end
The behavior of your code is called a side effect - a change to the program's state that isn't a core part of the function. Side effects are generally to be avoided.

Crazy behavior (at least to me) when setting one string to another in Ruby

As a novice it's probably something about Ruby I missed, but for the life of me I don't understand this result. So I have this simple function:
def crazyfunc(s)
s.gsub!('a', 'b')
#return has not purpose here
end
Now I have this simple few sets.
s1 = 'abc'
s2 = s1
s2 = crazyfunc(str2)
puts s1
=> bbc
Why in the world is s1 affected by crazyfunc? So doing this instead:
def crazyfunc(s)
return s.gsub('a', 'b')
end
doesn't change str1, so I figure it's got to do with what the inplace gsub is doing. But I still don't get the logic of why str1 would be changed.
String assignment in Ruby doesn't implicitly copy the string. You are simply assigning another reference to it. If you want to copy the string, use clone.
To demonstrate, you can check object IDs:
ree-1.8.7-2010.02 > a = "foo"
=> "foo"
ree-1.8.7-2010.02 > b = a
=> "foo"
ree-1.8.7-2010.02 > a.object_id
=> 81728090
ree-1.8.7-2010.02 > b.object_id
=> 81728090
Since a and b have the same object ID, you know they're the same object. If you want to modify b as a copy of a, you can either use methods which return a new string (like gsub rather than gsub!), or you can use b = a.clone, and then operate on b.
ree-1.8.7-2010.02 > a = "foo"
=> "foo"
ree-1.8.7-2010.02 > b = a.clone
=> "foo"
ree-1.8.7-2010.02 > a.object_id
=> 81703030
ree-1.8.7-2010.02 > b.object_id
=> 81696040
ree-1.8.7-2010.02 > b.gsub! "f", "e"
=> "eoo"
ree-1.8.7-2010.02 > a
=> "foo"
ree-1.8.7-2010.02 > b
=> "eoo"
Or more simply:
ree-1.8.7-2010.02 > a = "foo"
=> "foo"
ree-1.8.7-2010.02 > b = a.gsub("f", "e")
=> "eoo"
ree-1.8.7-2010.02 > puts a, b
foo
eoo
You are running into the fact that everything in Ruby is an object, and that variables are just object references.
When you assign str1 = to str2, you are actually pointing them at the same object. When you then change the object pointed to by str2, you are also changing the object pointed to by str1.
In your original crazyfunc, you modify the string, return the modified string, and then change the value of the object pointed to by str2 - and str1, since they point to the same object.
s1 = 'abc'
s2 = s1
s2 = crazyfunc(str2)
A #master
B = A
C = B
A is A, and B equals A and C equals B. If you change B it doesn't affect A but affects C. See how it cascades down. The variables you list are just pointers.
So you only have 1 master variable here and you need to declare two.

Resources