Use of = vs .dup method - ruby

array = [1,2,3,4,5]
array1 = array
array2 = array.dup
puts array1 == array2
Why do we have a dup method when we can just assign to another variable?

array = [1,2,3,4,5]
array1 = array
array2 = array.dup
array << "aha"
p array1 # => [1, 2, 3, 4, 5, "aha"]
p array2 # => [1, 2, 3, 4, 5]

You're fooling yourself by:
Trying to reason from a single example.
Comparing the wrong things.
Array has its own == method that compares element by element so given:
a = [ 11 ]
b = [ 11 ]
then a == b is true even though a and b reference different arrays.
In general, = simply copies a reference similar to this in C:
int *i, *j;
i = j;
but dup makes a (shallow) copy.
If you compare the object_ids:
puts array1.object_id == array2.object_id
you'll see that the underlying array objects are different even though == says that the have equal contents.

A statement like:
array1 = array
just assigns a reference to array1 from array. This means that both array and array1 point to the same memory location. If you change the underlying array, it will be reflected in both copies:
irb(main):001:0> array = [1,2,3]
=> [1, 2, 3]
irb(main):002:0> array1 = array
=> [1, 2, 3]
irb(main):003:0> array
=> [1, 2, 3]
irb(main):004:0> array1
=> [1, 2, 3]
irb(main):005:0> array[0] = 10
=> 10
irb(main):006:0> array
=> [10, 2, 3]
irb(main):007:0> array1
=> [10, 2, 3]
If you use dup, it clones the underlying data, creating new, independent storage:
irb(main):008:0> array2 = array.dup
=> [10, 2, 3]
irb(main):009:0> array
=> [10, 2, 3]
irb(main):010:0> array2
=> [10, 2, 3]
irb(main):011:0> array2[0] = 20
=> 20
irb(main):012:0> array
=> [10, 2, 3]
irb(main):013:0> array2
=> [20, 2, 3]

Related

How can I modify an array without destroying an array assigned to it?

Consider the following:
array1 = [1, 2, 3, 4]
array2 = array1 # => [1, 2, 3, 4]
array2.pop
array2 # => [1, 2, 3]
array1 # => [1, 2, 3]
Why is array1 destroyed when I've only called pop on array2? Is there a way to pop the last value from array2 and leave array1 intact so that I get array1 # => [1, 2, 3, 4]?
It's an aliasing issue. Your references point to the same Array object in memory. If your arrays contain simple Integers like those dup method do the trick.
array2 = array1.dup
array2 = array1.dup
array2 = array1.clone => Your changes effects both arrays
I prefer Object#dup method, but here is one more option FYI:
> array1 = [1, 2, 3, 4]
#=> [1, 2, 3, 4]
> array2 = Array.new + array1
#=> [1, 2, 3, 4]
> array1.object_id
#=> 87422520
> array2.object_id
#=> 87400390

I am trying to split the array into two parallel array

I have an array, and i need to split it into two, one after another scenario.
number = [1,2,3,4,5,6,7,8,9]
I need to slip it into two like following
split1 = [2,4,6,8]
split2 = [1,3,5,7,9]
A modification on Arup's answer:
split1, split2 = [1,2,3,4,5,6,7,8,9].partition.with_index{|_, i| i.odd?}
split1 # => [2, 4, 6, 8]
split2 # => [1, 3, 5, 7, 9]
split1, split2 = %i[a b c d e].partition.with_index{|_, i| i.odd?}
split1 # => [:b, :d]
split2 # => [:a, :c, :e]

why Hash value gets affected in Ruby

mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
indval = ahash.shift
ahash become as follows
[1, 2, 3, 4]
and mainhash become as follows
{"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
I am manipulating ahash variable by shifting some values from ahash, When I do this operation it affects the mainhash value. Why it is happening?
Am I missing any conceptual understanding?
It's because ahash and mainhash both have references to the same Array instance. If you modify this through ahash, referenced object is being modified, so no wonder it changes also in mainhash.
To operate on copy (shallow copy, to be precise) of the object instead of the same object, you should use dup method:
ahash = mainhash['A'].dup
Look Array#shift
Removes the first element of self and returns it (shifting all other elements down by one). Returns nil if the array is empty.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
p ahash.object_id # => 8577888
p mainhash['A'].object_id # => 8577888
p indval = ahash.shift # => 0
As above seen, ahash and mainhash['A'] refer to the same Array object [ 0,1,2,3,4], thus changing ahash#shift causes 0 to be removed from ahash which also causes 0 to be removed from mainhash['A'].
Said that your Hash becomes as below :
mainhash
# => {"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
All operations are legitimate and happened as documented to the link,I have given above.
How can I avoid affecting the mainhash
As #Marek Lipka said :
you should use dup method: ahash = mainhash['A'].dup.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A'].dup
ahash.object_id # => 8577516
mainhash['A'].object_id # => 8577600
indval = ahash.shift # => 0
ahash # => [1, 2, 3, 4]
mainhash['A'] # => [0, 1, 2, 3, 4]
Why it is happening ?.
arr = [1, 2, 3]
x = arr
arr.shift
p arr
p x
--output:--
[2, 3]
[2, 3]
arr and x both refer to the same array. Assignment ('=') does not create a copy.
Now look at this code:
arr = [1, 2, 3]
x = arr.dup
arr.shift
p arr
p x
--output:--
[2, 3]
[1, 2, 3]
And by the way, the name 'ahash' is a terrible name for an array.

Create a hash using two arrays

I need to create a new Hash object using two arrays.
But, the conditions is first array value should be a key value for the Hash and second array value should be the Hash value.
a = ["x", "y"]
b = [2, 4]
Result should be: c = {"x" => 2, "y" => 4}
irb(main):001:0> a = ["x", "y"]; b = [2, 4]
=> [2, 4]
irb(main):002:0> Hash[a.zip(b)]
=> {"x"=>2, "y"=>4}

Is there a particular function to retrieve then delete random array element?

I know I can do this in a couple of steps, but was wondering if there is a function which can achieve this.
I want to array#sample, then remove the element which was retrieved.
How about this:
array.delete_at(rand(array.length))
Another inefficient one, but super obvious what's going on:
array.shuffle.pop
What would be nice would be a destructive version of the sample method on Array itself, something like:
class Array
def sample!
delete_at rand length
end
end
Linuxios's has it perfect. Here is another example:
array = %w[A B C]
item_deleted = array.delete_at(1)
Here it is in irb:
1.9.2p0 :043 > array = %w[A B C]
=> ["A", "B", "C"]
1.9.2p0 :044 > item_deleted = array.delete_at(1)
=> "B"
1.9.2p0 :045 > array
=> ["A", "C"]
1.9.2p0 :047 > item_deleted
=> "B"
An alternative to the rand(array.length) approach already mentioned, could be this one
element = array.delete array.sample
Eksample:
>> array = (1..10).to_a
>> element = array.delete array.sample
>> array # => [1, 2, 4, 5, 6, 7, 8, 9, 10]
>> element # => 3
This is also a set of two operations, but at least you won't have to move away from the array itself.
If you need to sample a number of items and the remove those from the original array:
array = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
grab = array.sample(4)
=> [2, 6, 10, 5]
grab.each{ |a| array.delete a }
=> [2, 6, 10, 5]
array
=> [1, 3, 4, 7, 8, 9]

Resources