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

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.

Related

How do I find the value of the key that I set with argument in Ruby?

I am currently working on a project with Ruby.
I don't know how to find the set of the value and key that has the same key as I gave with argument.
what I have tried is something like this below.
but it doesn't work.
def find_target(type)
target = type_list.find{|k,v| k == type}
target
end
def type_list
type = {
a: 1,
b: 2,
c: 3,
d: 4
}
type
end
but instead of giving an argument of variable, I gave a string as an argument, and it worked.
def find_target(a)
target = type_list.find{|k,v| k == a}
target
end
Edited
What I really want find_target to do is returning a matched value.
For example, when an argument is a, then it returns 1.
How can I solve this?
I would love you if you could help me.
Thank you .
I think one thing tripping you up is that your type_list hash is keyed with symbols (rather than strings or the value of a variable). The syntax you're using:
{a: 1}
is just shorthand for this:
{:a => 1}
Which means "A Hash with one key: the symbol :a with the value 1". That's distinct from:
{'a' => 1} # Keyed with the string 'a'
and this:
a = 'something'
b = {a => 1} # Keyed with value of 'a' at the time of creating, ie: {'something' => 1}. Note that if you change the value of a, the hash key won't change.
What do you expect as your return value from find_target(:a)? The find method on a Hash will return an Enumerator - mostly equivalent to a two-element Array, with the key and the value: {a: 1}.find{|k,v|k == :a} will return [:a, 1]. Is that what you want?
If you just want to have the value 1 returned, then you're really doing a hash lookup, and you don't need any extra methods at all. A common way to do this would be to define type_list as a constant, and then just refer to it by key:
TYPE_LIST = {
a: 1,
b: 2,
c: 3,
d: 4
}
#Then to find the type:
TYPE_LIST[:a] # Returns '1'
You might want to use a find_type method to handle the case where the key doesn't match a type: a plain Hash lookup will return nil, which you might not want.
Hope this helps put you on the right path, but I'm happy to expand this answer if needed!

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"

Reassigned hash changes the original hash

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)

How to return two separate arrays of keys and values

Please explain how this piece of code can return two arrays.
def keysAndValues(data)
[data.keys, data.values]
end
keysAndValues method does return a single array (of two arrays inside of it), but its output can be interpreted as two arrays. Let me explain:
single_array = ["hello", "world"]
puts single_array # => ["hello", "world"]
first_element, second_element = single_array
puts first_element # => "hello"
puts second_element # => "world"
The reason this notation is possible is the implementation of Ruby's assignment (=) operator. Thus, calling a, b = keysAndValues(data) makes both variables a and b filled. But beware, although the outcome technically makes sense this might be unexpected in some situations:
first, second = 1 # first is 1, second is nil
There are also some other uses for multiple assignment, consider the following case:
a, b, *c = [1, 2, 3, 4] # note the asterisk symbol here
puts a # => 1
puts b # => 2
puts c # => [3,4]
Ruby supports parallel assignment. This is a simple example:
foo, bar = 1, 2
foo # => 1
bar # => 2
Your method is returning two values inside an array:
keys_and_values(
{
a: 1,
b: 2
}
).size # => 2
which are assigned via = to the two values on the left side of the equation. The values in the array are references to where the keys and values sub-arrays are located:
foo, bar = keys_and_values(
{
a: 1,
b: 2
}
)
foo.object_id # => 70159936091440
bar.object_id # => 70159936091420
Ruby isn't unique with supporting parallel assignment; It is used in other languages too. Any decent Ruby manual will talk about this. It's good to understand because the assignment to variables, or passing multiple parameters to methods is something you'll encounter repeatedly in Ruby. Do a search for more information.
Also, in Ruby we don't name methods using camelCase, such as "keysAndValues". Instead we use snake_case: keys_and_values.

Pass arguments by reference to a block with the splat operator

It seems that the arguments are copied when using the splat operator to pass arguments to a block by reference.
I have this:
def method
a = [1,2,3]
yield(*a)
p a
end
method {|x,y,z| z = 0}
#=> this puts and returns [1, 2, 3] (didn't modified the third argument)
How can I pass these arguments by reference? It seems to work if I pass the array directly, but the splat operator would be much more practical, intuitive and maintainable here.
In Ruby when you write x = value you are creating a new local variable x whether it existed previously or not (if it existed the name is simply rebound and the original value remains untouched). So you won't be able to change a variable in-place this way.
Integers are immutable. So if you send an integer there is no way you can change its value. Note that you can change mutable objects (strings, hashes, arrays, ...):
def method
a = [1, 2, "hello"]
yield(*a)
p a
end
method { |x,y,z| z[1] = 'u' }
# [1, 2, "hullo"]
Note: I've tried to answer your question, now my opinion: updating arguments in methods or blocks leads to buggy code (you have no referential transparency anymore). Return the new value and let the caller update the variable itself if so inclined.
The problem here is the = sign. It makes the local variable z be assigned to another object.
Take this example with strings:
def method
a = ['a', 'b', 'c']
yield(*a)
p a
end
method { |x,y,z| z.upcase! } # => ["a", "b", "C"]
This clearly shows that z is the same as the third object of the array.
Another point here is your example is numeric. Fixnums have fixed ids; so, you can't change the number while maintaining the same object id. To change Fixnums, you must use = to assign a new number to the variable, instead of self-changing methods like inc! (such methods can't exist on Fixnums).
Yes... Array contains links for objects. In your code when you use yield(*a) then in block you works with variables which point to objects which were in array. Now look for code sample:
daz#daz-pc:~/projects/experiments$ irb
irb(main):001:0> a = 1
=> 1
irb(main):002:0> a.object_id
=> 3
irb(main):003:0> a = 2
=> 2
irb(main):004:0> a.object_id
=> 5
So in block you don't change old object, you just create another object and set it to the variable. But the array contain link to the old object.
Look at the debugging stuff:
def m
a = [1, 2]
p a[0].object_id
yield(*a)
p a[0].object_id
end
m { |a, b| p a.object_id; a = 0; p a.object_id }
Output:
3
3
1
3
How can I pass these arguments by reference?
You can't pass arguments by reference in Ruby. Ruby is pass-by-value. Always. No exceptions, no ifs, no buts.
It seems to work if I pass the array directly
I highly doubt that. You simply cannot pass arguments by reference in Ruby. Period.

Resources