Why does this array initialize behave differently? [duplicate] - ruby

This question already has an answer here:
Return values were not extracted in Ruby
(1 answer)
Closed 8 years ago.
I have
def initialize
#board = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
end
I thought I could shorten it to:
def initialize
#board=[ [0] * 4] * 4
end
and they look the same, but only with the latter, I get an error like:
10) vertical turn can add 1 to an existing column of 2 with 1's at the ends [20/18949]
Failure/Error: expect([game.board[0][2], game.board[1][2], game.board[2][2], game.board[3][2]]).to eq [0,1,0,0]
expected: [0, 1, 0, 0]
got: [1, 1, 0, 1]
(compared using ==)
# ./spec/game_spec.rb:132:in `block (2 levels) in <top (required)>'

Try this for your working and non-working code:
#board.map { |x| x.object_id }
and you will see the difference. The error is occurring because all the inner Arrays are the same object.
The problem is that
[ [0] * 4 ] * 4
is an Array which contains the same Array four times.
Whilst
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
is an Array that contains four different Array objects.
When you .inspect them they look the same because Ruby doesn't show you the object ids.
Another way to implement your 0-ed Array might be:
Array.new(4) { Array.new(4) {0} }
Or, if you are dealing exclusively with 4x4 integers, you could take a look at the narray library, which does not have these kinds of issues, and is also very fast for bulk operations.

Related

loop updating the entire column of the 2D array in ruby

I'm running the following code with Ruby version 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux]:
table = Array.new(3, Array.new(3, 0))
for i in 1..2
table[i][0] = i
end
p table
I expect this to print:
[
[0, 0, 0],
[1, 0, 0],
[2, 0, 0]
]
Instead I'm somehow getting:
[
[2, 0, 0],
[2, 0, 0],
[2, 0, 0]
]
I can reproduce this with other loops. Any idea what's going on here?
Any idea what's going on here?
Your 3 inner arrays are actually all the same object. (See Common gotchas)
To get an array of 3 different arrays, you have to pass a block to Array.new:
table = Array.new(3) { Array.new(3, 0) }
The inner array Array.new(3, 0) doesn't need the block form because 0 is immutable.

Odd behavior with Ruby arrays?

I am using Ruby 2.3.1 and I cannot tell if I've encountered a bug or if this is intended behavior.
If you create an NxN matrix by making nested arrays, as such:
matrix = [[0]*5]*5
and then set the elements on the diagonals, as such:
(0..4).each {|i| matrix[i][i] = i}
this ends up affecting every column in every row:
[
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]
]
Is this intended behavior?
P.S. I do not want to use Ruby's Matrix library, but would rather work with plain arrays.
Thanks in advance :)
In Ruby, arrays are, behind the scenes, objects of type array, which can contain primitive types and references to other objects. Now, this last bit is important - the array doesn't contain the object itself, but instead a pointer to it, which is interpreted as necessary when the programmer asks for it.
So the OP's original initialization code
matrix = [[0]*5]*5
Really creates a single array object containing 5 0s, and then copies the pointer to it 5 times. This also happens when you do
matrix = Array.new(5, Array.new(5, 0))
for precisely the same reason. So, as posted in the comments, the idiomatically correct Ruby way to create an array of 5 different array objects is
matrix = Array.new(5){Array.new(5, 0)}
Which yields a single array that contains pointers to 5 different array objects, preventing the issue encountered by the OP. Full documentation on the behaviour of Ruby arrays can be found at this finely-crafted link.
You don't need to change the diagonal to observe that behaviour; just change any element, say
matrix[1][1] = 1
Then
matrix
#=> [[0, 1, 0, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0],
# [0, 1, 0, 0, 0], [0, 1, 0, 0, 0]]
Consider
matrix.map { |row| row.object_id }
#=> [70153694327100, 70153694327100, 70153694327100,
# 70153694327100, 70153694327100].
This shows that all elements ("rows") of matrix are the same object, ergo, if that object is changed, all elements of matrix are affected. matrix = [[0]*5]*5 is equivalent to
matrix = Array.new(5, Array.new(5,0))
(See Array::new, expecially "Common gotchas".) What you want (as #Sebastian notes) is
matrix = Array.new(5) { Array.new(5,0) }
#=> [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
so that
matrix[1][1] = 1
only affects that one element:
matrix
#=> [[0, 0, 0, 0, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0],
# [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
matrix = [[0]*5]*5
Let's break this down:
a = [0]*5
Create an array of 5 zeros; this is an array of integers.
matrix = [a] * 5
Create an array of 5 references to the same array a.
So of course when you modify one, the others will be modified; it's the same array.
I don't know Ruby, so please feel free to correct any incorrect terminology.

Constructing multidimensional array

I'm working on a problem for an online bootcamp. I can't seem to figure out how to pass a multidimensional array into the constructor. I've looked around on here for a solution but I can't seem to find anything ruby specific to initializing a multidimensional array. Maybe I'm using the wrong search terms.
[Problem]
Build a class that allows us to build a new image with the data we specify. Make it possible for the class to output the image to the screen. Your goal is to have the following code work:
image = Image.new([
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]])
image.output_image
And when you run that code it will output the following to the terminal window:
0000
0100
0001
0000
Here is what I have written.
class Image
def initialize
#image= Array.new(4) { Array.new(4) }
end
def output_image
#image.each do |x|
x.each do |y|
puts y
end
end
end
end
image = Image.new([
[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]
])
image.output_image
I'm getting the error:
app.js:2:in `initialize': wrong number of arguments (1 for 2) (ArgumentError)
Any help would be much appreciated!
Thanks.
The problem is that your initialize method doesn't take any arguments. You can't pass an argument to a method that doesn't take any arguments.
Change this:
def initialize
#image = Array.new(4) { Array.new(4) }
end
To this:
def initialize(image)
#image = image
end

Why does my diagonal matrix calculator not return the total?

My method should take an array of subarrays, find the sum of the first value of the first array, the second value of the second array, the third value of the third array, and so on. Some examples of inputs and expected results are as follows:
exampleArray = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
]
diagonalSum(exampleArray) # => 4
exampleArray = [
[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 1]
]
diagonalSum(exampleArray) # => 5
I wrote this:
def diagonalSum(matrix)
total = 0
counter = 0
while matrix.length <= counter + 1 do
total += matrix[counter][counter]
counter += 1
end
total
end
and it returns 0.
It's easiest to convert the array to a matrix and apply Matrix#trace.
require 'matrix'
arr = [[1, 0, 0, 7],
[0, 2, 0, 0],
[0, 0, 3, 0],
[8, 0, 0, 4]]
Matrix[*arr].trace
#=> 10
According to the code you provide, in which the input is an array of arrays, the first advice I could give you is that in Ruby you must avoid using for/while loops and make use of iterators such as each/each_with_index instead (based on this Ruby style guide and the suggestions of #tadman and #Yu Hao).
The each with index iterator takes a Ruby block with the current array of the iteration along with its index position, so you don't need to define your own index variable and update it in every iteration.
Applying this to your code will result in the following:
def diagonal_sum(matrix)
total = 0
matrix.each_with_index do |row, index|
total+=row[index]
end
total
end
Also note that the convention in Ruby is to write variable and method names in snake_case (according to the previous style guide).

Filling/Populating a multidimensional array,, with elements of another array, and adding/incrementing values?

In Ruby I have a multidimensional array, 4D;
choices[ii][jj][kk][ll].
I have another array;
data[kk][ll]
choices[ii][jj].size==data.size
choices[ii][jj][kk].size==data[kk].size
I would like to populate the last two dimensions of choices with the elements of data, as they are the same size I am looking for an approach which requires little code.
In MATLAB I know how to do this easily with subscript indexing; eg. choices[ii,jj,:,:]=data[:,:]. As well, in MATLAB I can add the matrix/2Darray easily.
How can this be done in Ruby?
EDIT: an example
bb=Array.new(2,4)
aa=Array.new(3,Array.new(2,0))
=> [[0, 0], [0, 0], [0, 0]]
irb(main):006:0> aa[1]=bb
=> [4, 4]
irb(main):007:0> aa
=> [[0, 0], [4, 4], [0, 0]]
This is ok, but for a 3D array, I get the array 'bb' copied into sections I don't want it to:
aa=Array.new(3,Array.new(2,Array.new(2,0)))
=> [[[0, 0], [0, 0]], [[0, 0], [0, 0]], [[0, 0], [0, 0]]]
irb(main):013:0> aa[2][1]=bb
=> [4, 4]
irb(main):014:0> aa
=> [[[0, 0], [4, 4]], [[0, 0], [4, 4]], [[0, 0], [4, 4]]]
I read the documentation on Array more carefully.
An array can also be created by explicitly calling ::new with zero, one (the initial size of the Array) or two arguments (the initial size and a default object).
ary = Array.new #=> []
Array.new(3) #=> [nil, nil, nil]
Array.new(3, true) #=> [true, true, true]
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.
To create an array with separate objects a block can be passed instead. This method is safe to use with mutable objects such as hashes, strings or other arrays:
Array.new(4) { Hash.new } #=> [{}, {}, {}, {}]
This is also a quick way to build up multi-dimensional arrays:
empty_table = Array.new(3) { Array.new(3) }
#=> [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]
Which means that I was modifying the element which was referenced in multiple places. And I had to explicitly create a new Array object for each entry along the new dimensions I was adding, so in a way building the extra dimension incrementally. There seem to be multiple ways of doing this. One is this method of a block sent to Array, and another is to do an iterator on the dimension and make a new Array for each entry.
I also found this post which highlights the problem in very simple terms:>>
The wrong way, and the way I see people trying often is to say Array.new( 4, Array.new(4, 0) ). In other words, an array of 4 rows, each row being an array of 4 zeroes. And this appears to work at first. However, run the following code:
a = Array.new( 4, Array.new(4, 0) ) a[0][0] = 1 pp a
It looks simple. Make a 4x4 array of zeroes, set the top-left element to 1. But print it and we get…
[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]
It set the entire first column to 1, what gives? When we made the arrays, the inner-most call to Array.new gets called first, making a single row. A single reference to this row is then duplicated 4 times to fill the outer-most array. Each row is then referencing the same array. Change one, change them all.
Instead of passing a value to the Array.new method, we pass a block. The block is executed every time the Array.new method needs a new value. So if you were to say Array.new(5) { gets.chomp }, Ruby will stop and ask for input 5 times. So all we need to do is just create a new array inside this block. So we end up with Array.new(4) { Array.new(4,0) }. Now let's try that test case again.
a = Array.new(4) { Array.new(4, 0) } a[0][0] = 1 pp a
And it does just as you'd expect.
[[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

Resources