Ruby: How to Copy a Variable Without Pointing to the Same Object

Ruby: how can I copy a variable without pointing to the same object?

As for copying you can do:

phrase2 = phrase1.dup

or

# Clone: copies singleton methods as well
phrase2 = phrase1.clone

You can do this as well to avoid copying at all:

phrase2 = phrase1.gsub("Hello","Hi")

Copy object without any pointers in Ruby

I think #clone or #dup won't work because they will share a reference to @value inside Foo.

In any case, you can do it more readably by changing Foo#change_value so it doesn't actually mutate the object but returns a copy:

class Foo
def initialize(value = nil)
@value = value || rand(10)
end

def change_value(input)
# returns a new Foo instance
Foo.new(@value + 1)
end

def value
@value
end
end

Because you're copying data in any case, using an immutable object (Value Object) is more general than some kind of deep clone.

How to copy one variable into another and make them independent from each other

One way is to do one-level-deeper clone, i.e. duplicate child items as well.

arrays = [[15, 2, 3], [9, 1, 2], [5, 3, 0]]

sorted_clone = arrays.map {|item| item.clone}

sorted_clone.each do |i|
i.sort!
end

p arrays
p sorted_clone

Another way is to create new objects, instead of mutating existing ones:

arrays = [[15, 2, 3], [9, 1, 2], [5, 3, 0]]

# #sort method is the not-modifying-in-place version of #sort!
sorted_clone = arrays.map {|child| child.sort }

p arrays
p sorted_clone

How to create a deep copy of an object in Ruby?

Deep copy isn't built into vanilla Ruby, but you can hack it by marshalling and unmarshalling the object:

Marshal.load(Marshal.dump(@object))

This isn't perfect though, and won't work for all objects. A more robust method:

class Object
def deep_clone
return @deep_cloning_obj if @deep_cloning
@deep_cloning_obj = clone
@deep_cloning_obj.instance_variables.each do |var|
val = @deep_cloning_obj.instance_variable_get(var)
begin
@deep_cloning = true
val = val.deep_clone
rescue TypeError
next
ensure
@deep_cloning = false
end
@deep_cloning_obj.instance_variable_set(var, val)
end
deep_cloning_obj = @deep_cloning_obj
@deep_cloning_obj = nil
deep_cloning_obj
end
end

Source:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/43424

Does Ruby not create a duplicate variable a of b, when a = b?

Let me try to clarify exactly what's going on here. This is the basic concept of references, as implemented by many languages. When you create, say, "an array," that array-value is an anonymous memory-object, and in your code both a and b refer to the same object. The language uses some variation of "reference counting" to keep track of how many references exist, so as to know when values have been "orphaned" and can be garbage-collected.

This so-called "shallow copying" is very efficient and it is often exactly what is wanted. So, it's what these languages do by default.

When you create a "deep copy," you are consciously duplicating the anonymous memory-object. Now, a points to one, and b points to another object that is (at that instant in time) a snapshot of the first. A time-consuming process by comparison, and, of course, it allocates more memory. Now, changes made to one array will (of course) not be reflected in the other, since they are now two entirely distinct things. Since this is a less-common thing to do, the language provides some explicit way for you to designate that you want to do this.

Ruby ... PHP ... Perl ... ... ... almost everybody does something like this.

(And, en passant, let me also say that they implement "strong" vs. "weak" references. But, that's another story for another day.)

EDIT: This response is partially incorrect! Kindly note the correction that was subsequently made in the first comment attached to this post. (And, "thank you for setting me straight!") The key point ( and it IS a key point ...), has to do with what the anonymous memory-objects aforementioned might contain. (They might, themselves, contain "references." Arrays often do! So, you could wind up with "two separate memory objects" which refer to the same things. Ergo, even though they are, indeed, "two entirely separate objects," they still can conflict with one another when actually used in your program. (And, heh, have fun debugging such things.)

Ruby: how can I copy this array?

Modifying the duped array will not affect the original. However modifications to the strings inside the array will be visible globally because the global array and the duped array still contain references to the same strings (dup does not perform a deep copy).

So either perform a deep copy (svg_filedata_cache = $svg_filedata_cache.map {|line| line.dup}) or simply avoid mutating operations on the strings.

Ruby: How to use dup/clone to not mutate an original instance variable?

This is a common newbie mistake.

Suppose

a = [1, 2, 3]
b = a.dup
#=> [[1, 2], [3, 4]]
b[0] = 'cat'
#=> "cat"
b #=> ["cat", 2, 3]
a #=> [1, 2, 3]

This is exactly what you were expecting and hoping for. Now consider the following.

a = [[1, 2], [3, 4]]
b = a.dup
#=> [[1, 2], [3, 4]]
b[0] = 'cat'
b #=> ["cat", [3, 4]]
a #=> [[1, 2], [3, 4]]

Again, this is the desired result. One more:

a = [[1,2], [3,4]]
b = a.dup
#=> [[1,2], [3,4]]
b[0][0] = 'cat'
b #=> [["cat", 2], [3, 4]]
a #=> [["cat", 2], [3, 4]]

Aarrg! This is the problem that you experienced. To see what's happening here, let's look the id's of the various objects that make up a and b. Recall that every Ruby object has a unique Object#id.

a = [[1, 2], [3, 4]]
b = a.dup
a.map(&:object_id)
#=> [48959475855260, 48959475855240]
b.map(&:object_id)
#=> [48959475855260, 48959475855240]
b[0] = 'cat'
b #=> ["cat", [3, 4]]
a #=> [[1, 2], [3, 4]]
b.map(&:object_id)
#=> [48959476667580, 48959475855240]

Here we simply replace b[0], which initially was the object a[0] with a different object ('cat') which of course has a different id. That does not affect a. (In the following I will give just the last three digits of id's. If two are the same the entire id is the same.) Now consider the following.

a = [[1, 2], [3, 4]]
b = a.dup
a.map(&:object_id)
#=> [...620, ...600]
b.map(&:object_id)
#=> [...620, ...600]
b[0][0] = 'cat'
#=> "cat"
b #=> [["cat", 2], [3, 4]]
a #=> [["cat", 2], [3, 4]]
a.map(&:object_id)
#=> [...620, ...600]
b.map(&:object_id)
#=> [...620, ...600]

We see that the elements of a and b are the same objects as they were before executing b[0][0] = 'cat'. That assignment, however, altered the value of the object whose id is ...620, which explains why a, as well as b, was altered.

To avoid modifying a we need to do the following.

a = [[1, 2], [3, 4]]
b = a.dup.map(&:dup) # same as a.dup.map { |arr| arr.dup }
#=> [[1, 2], [3, 4]]
a.map(&:object_id)
#=> [...180, ...120]
b.map(&:object_id)
#=> [...080, ...040]

Now the elements of b are different objects than those of a, so any changes to b will not affect a:

b[0][0] = 'cat'
#=> "cat"
b #=> [["cat", 2], [3, 4]]
a #=> [[1, 2], [3, 4]]

If we had

a = [[1, [2, 3]], [[4, 5], 6]]

we would need to dup to three levels:

b = a.map { |arr0| arr0.dup.map { |arr1| arr1.dup } }
#=> [[1, [2, 3]], [[4, 5], 6]]
b[0][1][0] = 'cat'
b #=> [[1, ["cat", 3]], [[4, 5], 6]]
a #=> [[1, [2, 3]], [[4, 5], 6]]

and so on.

Is .dup really creating a shallow copy?

You forget that == is actually a method from BasicObject:

obj == other → true or false

Equality — At the Object level, == returns true only if obj and other are the same object. Typically, this method is overridden in descendant classes to provide class-specific meaning.

So if you haven't provided your own implementation of == (i.e. a MyObject#== method) then your:

p myObject1 == myObject1.dup

is pretty much the same as saying:

p myObject1.object_id == myObject1.dup.object_id

and since myObject1.dup is a shallow copy of myObject1 (i.e. they're different objects), you get false.

When they say:

instance variables of obj are copied

they're referring to the instance variables inside obj, not variables that happen to reference obj. Your myObject1 isn't an instance variable in anything, it is just a variable, instance variables are referenced with a leading @ as in @my_instance_variable.

If you want == to behave they way you expect it to then you have to provide your own == implementation:

class MyObject
def ==(other)
# Check that the contents of `self` and `other` are the same
# and probably that `other.is_a?(MyObject)` first.
end
end

Why would you `replace` rather than assign a new object to the same variable?

The beauty of ruby is that in a lot of cases there is no right way to do things. In your case, I think your solution is just as valid as well as being easier to read.

As for replace, it works by copying values over into the existing array which is different then assigning it to a different object.

For example

x = [1, 2]
y = [3]
x.replace(y) # x is [3] and y is [3]
x << 5 # x is [3, 5] and y is [3]

x = [1, 2]
y = [3]
x = y # x is now pointing to the same object as y
x << 5 # both x and y are [3, 5] because they are the same object


Related Topics



Leave a reply



Submit