Arrays misbehaving - ruby

Here's the code:
# a = Array.new(3, Array.new(3))
a = [[nil,nil,nil],[nil,nil,nil]]
a[0][0] = 1
a.each {|line| p line}
With the output:
[1, nil, nil]
[nil, nil, nil]
but using the commented line:
[1, nil, nil]
[1, nil, nil]
[1, nil, nil]
So why is that?

The commented line is assigning three of the same reference to the array, so a change to one array will propagate across the other references to it.
As for the 2 arrays vs 3, that's simply a matter of the first line specifying 3 as its first parameter and only specifying 2 array literals in the second line.
To create the nested arrays without having any shared references:
a = Array.new(3) {Array.new(3)}
When passed a block ({...} or do ... end), Array.new will call the block to obtain the value of each element of the array.

Related

How can I modify positions in a two dimensional array?

If I do the following:
table = Array.new(
3,
Array.new(
3,
nil
)
)
# =>
[
[nil, nil, nil],
[nil, nil, nil],
[nil, nil, nil]
]
Now I would like to modify the value at index 2 in the second array, so I would do:
table[1][2] = 2.343
I would now expect to see:
# =>
[
[nil, nil, nil],
[nil, nil, 2.343],
[nil, nil, nil]
]
However what I'm getting is this:
[
[nil, nil, 2.343],
[nil, nil, 2.343],
[nil, nil, 2.343]
]
What am I not getting here?
PS: Running ruby 2.3
For fix with behavior, try next:
empty_table = Array.new(3) { Array.new(3) }
From array manual:
Note that the second argument populates the array with references to the same object. Therefore, it is only recommended in cases when you need to instantiate arrays with natively immutable objects such as Symbols, numbers, true or false.
you are essentially saying create an array with three elements, and put this element (the new array) in each space. The element you are putting in the first array, is just created once. The only way I know to do what you want is using a for loop to push as many new arrays into the first array as you need. something like this:
table = Array.new(1, Array.new(3, 0))
0..1.each do |i|
table.push(Array.new(3, 0)) #add two more arrays to the first dimension
end

Clone or duplicate the results array of Ruby's Scan method?

Suppose I pass the string "abcd" through Ruby's Scan method using the regular expression /(a)|(b)/. This would return an array:
>> results_orig = "abcd".scan(/(a)|(b)/)
#=> [["a", nil], [nil, "b"]]
Now, if I duplicate (.dup) or clone (.clone) this array,
>> results_copy = results_orig.dup
#=> [[["a", nil], [nil, "b"]]
and modify any element of this copy, the original array also gets modified!
>> results_copy[0][0]="hello"
#=> "hello"
>> results_copy
#=> [["hello", nil], [nil, "b"]]
>> results_orig
#=> [["hello", nil], [nil, "b"]]
This is strange, since, first, the arrays have different object IDs (results_orig.object_id == results_copy.object_id returns false), and, second, it does not happen if the array was not the product of the Scan method. To see the latter, consider the following example.
>> a = [1, 2, 3]
>> b = a.dup
>> b[0] = "hello"
>> a
#=> [1, 2, 3]
>> b
#=> ["hello", 2, 3]
My current solution is to run scan twice and catch each array in separate objects---that is, r_orig = "abca".scan(/(a)|(b)/)" ; r_copy = "abca".scan(/(a)|(b)/). But this is going to be very inefficient when I have to scan hundreds of strings.
Is there a proper way to duplicate the array from Scan's results that I can then modify whilst leaving the original results array unharmed?
Edit #1: I am running Ruby 2.0.0-p353 on Mac OS X 10.9.2.
Edit #2: It appears the issue exists when the array structure is nested... simple (single-level) arrays don't seem to have this problem. Corrected my example to reflect this.
You need to make a Deep copy. Check out this article for more information. Essentially, you need to do
copied_array = Marshal.load(Marshal.dump(complex_array))
Code source: http://thingsaaronmade.com/blog/ruby-shallow-copy-surprise.html. Marshalling works for arrays, but not for every object. A more robust method to perform a Deep copy is in the answer to this question.

Why does .map produce a row of nils when used to enumerate over hashes?

test =
{:content=>"type_name", :content_length=>9, :array_index=>0},
{:content=>"product_id", :content_length=>10, :array_index=>1},
{:content=>"First Item", :content_length=>10, :array_index=>0},
{:content=>"1111", :content_length=>4, :array_index=>1}
pp test.map {|x| puts x} #=>
{:content=>"type_name", :content_length=>9, :array_index=>0}
{:content=>"product_id", :content_length=>10, :array_index=>1}
{:content=>"First Item", :content_length=>10, :array_index=>0}
{:content=>"1111", :content_length=>4, :array_index=>1}
[nil, nil, nil, nil]
What is the cause of that array of nils? The map works perfectly, but then it causes these nils!
The trouble is that #map is designed to transform an array into a different array. Generally, the block of #map will not have side effects. Here's a use of #map to double all the numbers in an array:
[1, 2, 3].map { |n| n * 2} # => [2, 4, 6]
If the purpose of your loop is solely to have side effects (such as printing the elements), you want #each instead:
[1, 2, 3].each { |n| puts n }
# => 1
# => 2
# => 3
In this case, we don't care about the return value of #each. All we care about is that each number gets printed.
Argh what a stupid error!
This fixes it:
test.map {|x| puts x}
I was pretty printing the puts statement, and irb, trying to be helpful, returned nil four times!

Weird behavior on new (nested) Array in Ruby [duplicate]

This question already has an answer here:
Ruby Array Initialization [duplicate]
(1 answer)
Closed 3 years ago.
Why both pieces of code are not printing the same thing. I was intending the first piece to produce the output of the second
a=Array.new(5,Array.new(3))
for i in (0...a[0].length)
a[0][i]=2
end
p a
# this prints [[2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]]*
a=Array.new(5).map{|d|d=Array.new(3)}
for i in (0...a[0].length)
a[0][i]=2
end
p a
# this prints [[2, 2, 2], [nil, nil, nil], [nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]
This one
a=Array.new(5,Array.new(3))
Creates an array that contains the same array object within it five times. It's kinda like doing this:
a = []
b = a
a[0] = 123
puts b[0] #=> 123
Where this one:
a=Array.new(5).map{ Array.new(3) }
Creates a new 3 item array for each item in the parent array. So when you alter the first item it doesn't touch the others.
This is also why you shouldn't really use the Array and Hash constructor default arguments, as they don't always work they way you might expect.
Array.new(5,Array.new(3))
In the first example, your array contains 5 references to the same array. You create a single instance of an array with Array.new(3), a reference to which is used for each of the 5 arrays you initialize. When you modify a[0][0], you're also modifying a[1][0], a[2][0], etc. They are all references to the same array.
Array.new(5).map{ |d| Array.new(3) }
In the second example, your array contains 5 different arrays. Your block is invoked 5 times, Array.new(3) is invoked 5 times, and 5 different arrays are created. a[0] is a different array than a[1], etc.
The following are equivalent:
Array.new(5,Array.new(3))
[Array.new(3)] * 5
inside = Array.new(3); [inside, inside, inside, inside, inside]
They will all produce an array containing the same array. I mean the exact same object. That's why if you modify its contents, you will see that new value 5 times.
As you want independent arrays, you want to make sure that the "inside" arrays are not the same object. This can be achieved in different ways, for example:
Array.new(5){ Array.new(3) }
5.times.map { Array.new(3) }
Array.new(5).map { Array.new(3) }
# or dup your array manually:
inside = Array.new(3); [inside.dup, inside.dup, inside.dup, inside.dup, inside]
Note that the Array.new(5, []) form you used first doesn't copy the obj for you, it will reuse it. As that's not what you want, you should use the block form Array.new(5){ [] } which will call the block 5 times, and each time a new array is created.
The Hash class also has two constructors and is even more tricky.

Changing a few values in a hash

Say I have a hash:
h = {"upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4 }
and I want to get:
{"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 }
so I create a method that takes a hash:
def edge_adjust(hash)
hash["upper_left", nil, "upper_right", nil]
end
but I get the error:
wrong number of arguments (4 for 1)
I know it's giving the elements of the hash one at a time or my method is broke, not sure how to get what I want.
You may want to use merge method to replace first hash values with the values from the second hash:
def edge_adjust(hash)
hash.merge( {"upper_left", nil, "upper_right", nil})
end
edge_adjust({"upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4 })
# returns: {"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 }
Please not that if first hash does not contain some values from the second hash then these values will be created:
edge_adjust({"lower_left", 3, "lower_right", 4 })
# returns: {"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 } as well
Your Hash initialization is wrong. I suppose you want something like:
h = Hash["upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4]
["upper_left", "upper_right"].each{|k| h[k] = nil}
In this case, Hash#[] is an accessor method, not something that will modify the data. It takes only one argument, the key, and will return the value stored in that location, if any. This is not to be confused with Hash.[] which is a class method to create new hashes.
If you want to mass-assign values to the hash, you have a few options, but the most straight-forward is:
# Spin through a list of keys to remove...
%w[ upper_left upper_right ].each do |k|
# ...and nil out each entry.
h[k] = nil
end
You might also try and use a pattern to zap out any entries you don't want:
# Delete all keys that begin with "upper_"
h.delete_if { |k| k.match(/^upper_/) }
Note that this actually deletes the keys as well, so you can still get nil when fetching, but they are not present in h.keys.

Resources