Ruby References - ruby

When I have the following method:
def n_times(thing)
lambda { |n| thing * n }
end
and I call it like that:
x = [:a]
p1 = n_times(x)
x = [:b]
p p1.call(3) # => [:a, :a, :a]
x will not be changed, the output will be [:a]. Why?
When doing something like .pop instead, x will be changed:
x = [:a]
p1 = n_times(x)
x.pop
p p1.call(3) # => []
Is it because [:b] is a new object?

This is because x was assigned a new object in first case. You can check by inspecting the object_id
x = [:a]
p x.object_id # => //some number
p1 = n_times(x)
x = [:b]
p x.object_id # => //diff number
p p1.call(3) # => [:a, :a, :a]
x = [:a]
p x.object_id # => //some number
p1 = n_times(x)
x.pop
p x.object_id # => //same number
p p1.call(3) # => []
# Another example
x = [:a]
p x.object_id # => //some number
p1 = n_times(x)
x[0] = :b
p x.object_id # => //same number
p p1.call(3) # => [:b, :b, :b]

Is it because [:b] is a new object?
Yes. When you do x.pop, you're modifying the object that both x and thing refer to. When you do x = [:b], you make x refer to a new object. Making a variable refer to a new object doesn't affect other variables or objects.
Note that if the lambda closed over the variable x, it would be a different matter. In that case changing x would affect the lambda, but that's not the case. The lambda closes over the variable thing, which is a different variable that only happened to refer to the same object until x was reassigned.

Related

Ruby map! does not change contained variable

Suppose I have an object x, and an array y=[x] which contains x. If I manipulate x, then y does not change:
x = 1 # => 1
y = [x] # => [1]
x = x+1 # => 2
x # => 2
y # => [1]
and if I change y, then x does not change.
x = 1 # => 1
y = [x] # => [1]
y.map!{|a| a+1} # => [2]
y # => [2]
x # => 1
Is there a way to have them change in parallel? It feels like when I map! over an array the underlying values should change.
first of all
x = x + 1
will create a new variable with an old name x
x = 1
y = [x]
x.object_id
# 3
y[0].object_id
# 3
x = x + 1
x.object_id
# 5
y[0].object_id
# 3
Second, numbers are immutable objects in Ruby (and Strings, for example, are mutable). So you just can't do what you want using number objects in Ruby. What you can do is slightly more obscure. You can create your own mutable object (container for a number) and use a reference to this object in your array.
class MutableNumber
attr_accessor :n
def initialize(n)
#n = n
end
end
x = MutableNumber.new(1)
y = [x]
y[0].n
#=> 1
x.n += 1
y[0].n
#=> 2
You can go little bit further and to add more magic here to mimic numbers
class MutableNumber
attr_accessor :n
def initialize(n)
#n = n
end
def method_missing(m, *args, &blk)
#n = #n.send(m, *args, &blk)
self
end
end
x = MutableNumber.new(1)
y = [x]
y[0].n
#=> 1
x += 1
y[0].n
#=> 2
But I would not encourage you to do any of this.
Just stick to the idea that numbers are immutable. And overall you should be careful with mutability.
map! is mutable for the array, not for its elements. It means that the array gets new values in place (i.e. it's assigned to original array). Elements inside the array are not mutated, but replaced by new elements.
If you want to change old values, you can iterate using each and call mutating methods on each element. You can see this with array of strings:
a = "a"; b = "b"; aa = [a, b]
#=> ["a", "b"]
aa.map!{|e| e.capitalize }
#=> ["A", "B"]
[a, b]
#=> ["a", "b"]
a = "a"; b = "b"; aa = [a, b]
#=> ["a", "b"]
aa.each{|e| e.capitalize! }
#=> ["A", "B"]
[a, b]
#=> ["A", "B"]
Note that it won't work for immutable objects and numbers are immutable, as #fl00r explained in his answer.

Variable changing value, ruby

I am not sure how this variable called origString is changing value in my loop
def scramble_string(string, positions)
i = 0
origString = string
puts origString
newString = string
while i < string.length
newString[i] = origString[positions[i]]
i = i + 1
end
puts origString
return newString
end
for example if I run scramble_string("abcd", [3, 1, 2, 0])
origString changes from "abcd" in the first "puts" to "dbcd" in the second one.
How am I changing the value of origString if I am only declaring it once?
When you say x = y in Ruby that creates a variable with a reference to exactly the same object. Any modifications to x will apply to y and vice-versa:
y = "test"
x = y
x[0] = "b"
x
# => "best"
y
# => "best"
You can tell because of this:
x.object_id == y.object_id
# => true
They're identical objects. What you want is to make a copy first:
x = y.dup
x[0] = "b"
x
# => "best"
y
# => "test"
This results in two independent objects:
x.object_id == y.object_id
# => false
So in your case what you need is to change it like:
orig_string = string.dup
Now that being said, often the best way to process things in Ruby is by using functions that return copies, not manipulating things in place. A better solution is this:
def scramble_string(string, positions)
(0...string.length).map do |p|
string[positions[p]]
end.join
end
scramble_string("abcd", [3, 1, 2, 0])
"dbca"
Note that's a lot more succinct than the version with string manipulation.

equal? and eql? operator on Fixnum

As per definition, equal? checks if the two objects are same, where as eql? checks if the class are same and values are same.
x = 'hi'
y = 'hi'
x.equal? y # => false
x.eql? y # => true
x = 1
y = 1
x.equal? y # => true
x.eql? y # => true
Why is the second x.equal? y true? Aren't x and y two instances of Fixnum? Why doesn't it apply to Fixnum/Float as shown in the examples above?
Because x and y do actually refer to the exact same object. Unlike strings, each integer value has only one instance at any given time.
Reference: http://ruby-doc.org/core-2.2.1/Fixnum.html
There is effectively only one Fixnum object instance for any given integer value [...]
Edit:
To make it a bit more clear, you might want to look at the object_id for these objects:
irb(main):001:0> x = 1
=> 1
irb(main):002:0> y = 1
=> 1
irb(main):003:0> x.object_id
=> 3
irb(main):004:0> y.object_id
=> 3 # Same ID as above
irb(main):005:0> x = 'hi'
=> "hi"
irb(main):006:0> y = 'hi'
=> "hi"
irb(main):007:0> x.object_id
=> 70287051883000
irb(main):008:0> y.object_id
=> 70287051869720 # Different ID than X
I know this question already has been answered but I'll add this about object_id's
my_string << 'something' and my_string.replace 'something' and bang methods like my_string.strip! don't change the object_id that's why you can change a constant's content like MyString = 'test'
But MyString.freeze will prevent you from the constant beeing mutable.
It's good to try things like this to learn the language :
irb(main):024:0> x = '1'
=> "1"
irb(main):025:0> y = x
=> "1"
irb(main):026:0> x.equal? y
=> true
irb(main):027:0> x << 'test'
=> "1test"
irb(main):028:0> y
=> "1test"
irb(main):029:0> nope = nope
=> nil

Why isn't the value picked up in this Ruby inject call?

In the following code, if I don't add parentheses around the key, value parameters in "original.inject" (i.e., I do original.inject({}){ |result, key, value| ), I get a nil error as in the code comments below, as if the value is not being passed correctly. Why is this? What exactly is going on here? (I'm running ruby-2.1.1)
def hash_test(original,options={},&block)
original.inject({}){ |result, (key, value)|
value = value + 2
block.call(result, key, value)
result
}
end
h={:a=>3, :b=>4}
r = hash_test(h) { |result, key, value| result[key]=value }
puts r #=> {:a=>5, :b=>6}
#if no parentheses around (key, value) in original.inject, you get a:
# hash_transformer.rb:5:in `block in hash_test': undefined method `+' for nil:NilClass (NoMethodError)
# from hash_transformer.rb:4:in `each'
# from hash_transformer.rb:4:in `inject'
# from hash_transformer.rb:4:in `hash_test'
# from hash_transformer.rb:15:in `<main>'
When inject is called on a Hash inject yields the key value pairs as an array to the supplied block, example as below:
{:a=>3, :b=>4}.inject({}) do |x,y|
p y
x[y[0]] = y[1]
x
end
#>>[:a, 3]
#>>[:b, 4]
#=> {:a=>3, :b=>4}
y yielded the first time is [:a, 3], and second time is [:b, 4], so you need to destructure the array by supplying the parenthesis around the arguments:
(a,b) = [:a, 3]
a #=> :a
b #=> 3
This shows what's going on.
With deconstruction of hash element
{:a=>3, :b=>4}.inject({}) do |x,(y,z)|
puts "x = #{x}"
puts "y = #{y}"
puts "z = #{z}"
puts
x
end
x = {}
y = a
z = 3
x = {}
y = b
z = 4
Without deconstruction of hash element
{:a=>3, :b=>4}.inject({}) do |x,y,z|
puts "x = #{x}"
puts "y = #{y}"
puts "z.nil? = #{z.nil?}"
puts
x
end
x = {}
y = [:a, 3]
z.nil? = true
x = {}
y = [:b, 4]
z.nil? = true
Take a look at this post :
http://apidock.com/ruby/Enumerable/inject
It said inject is defined to take pairs parameter.
If you want to use three parameters, like (inject do |c,key,val|), then you should use parentheses to combine the (key,val)

make an object behave like an Array for parallel assignment in ruby

Suppose you do this in Ruby:
ar = [1, 2]
x, y = ar
Then, x == 1 and y == 2. Is there a method I can define in my own classes that will produce the same effect? e.g.
rb = AllYourCode.new
x, y = rb
So far, all I've been able to do with an assignment like this is to make x == rb and y = nil. Python has a feature like this:
>>> class Foo:
... def __iter__(self):
... return iter([1,2])
...
>>> x, y = Foo()
>>> x
1
>>> y
2
Yep. Define #to_ary. This will let your object be treated as an array for assignment.
irb> o = Object.new
=> #<Object:0x3556ec>
irb> def o.to_ary
[1, 2]
end
=> nil
irb> x, y = o
=> [1,2]
irb> x
#=> 1
irb> y
#=> 2
The difference between #to_a and #to_ary is that #to_a is used to try to convert
a given object to an array, while #to_ary is available if we can treat the given object as an array. It's a subtle difference.
Almost:
class AllYourCode
def to_a
[1,2]
end
end
rb = AllYourCode.new
x, y = *rb
p x
p y
Splat will try to invoke to_ary, and then try to invoke to_a. I'm not sure why you want to do this though, this is really a syntactical feature that happens to use Array in its implementation, rather than a feature of Array.
In other words the use cases for multiple assignment are things like:
# swap
x, y = y, x
# multiple return values
quot, rem = a.divmod(b)
# etc.
name, age = "Person", 100
In other words, most of the time the object being assigned from (the Array) isn't even apparent.
You can't redefine assignment, because it's an operator instead of a method. But if your AllYourCode class were to inherit from Array, your example would work.
When Ruby encounters an assignment, it looks at the right hand side and if there is more than one rvalue, it collects them into an array. Then it looks at the left hand side. If there is one lvalue there, it is assigned the array.
def foo
return "a", "b", "c" # three rvalues
end
x = foo # => x == ["a", "b", "c"]
If there is more than one lvalue (more specifically, if it sees a comma), it assigns rvalues successively and discards the extra ones.
x, y, z = foo # => x == "a", y == "b", z == "c"
x, y = foo # => x == "a", y == "b"
x, = foo # => x == "a"
You can do parallel assignment if an array is returned, too, as you have discovered.
def bar
["a", "b", "c"]
end
x = bar # => x == ["a", "b", "c"]
x, y, z = bar # => x == "a", y == "b", z == "c"
x, y = bar # => x == "a", y == "b"
x, = bar # => x == "a"
So in your example, if rb is an Array or inherits from Array, x and y will be assigned its first 2 values.

Resources