Search code examples
rubyscopeblock

Ruby Block won't change local variable


this code here:

def create_sym_matrix
  ran_arr = Array.new(5){rand(1..100)}
  ran_arr[1] = 0
  mat_arr = Array.new(5){|i|ran_arr.push(ran_arr.shift)}
  Matrix.columns(mat_arr)
end

aa = create_sym_matrix

=> Matrix[[86, 86, 86, 86, 86], [0, 0, 0, 0, 0], [20, 20, 20, 20, 20], 
[39, 39, 39, 39, 39], [48, 48, 48, 48, 48]]

Can someone explain to me why it did not rotate? I was trying to get a symmetric Matrix with zeros in the diagonal.


Solution

  • The documentation of Array::new explains:

    new(size=0, default=nil)
    new(array)
    new(size) {|index| block }
    ...

    In the last form, an array of the given size is created. Each element in this array is created by passing the element's index to the given block and storing the return value.

    The block {|i|ran_arr.push(ran_arr.shift)} always returns the same value: the array stored in ran_arr. The new array contains 5 references to ran_arr. The rows of mat_arr contain the same values because there aren't five different rows; it's the same object linked five times.

    To fix this you should return from the block a copy of ran_arr:

      mat_arr = Array.new(5){|i|ran_arr.push(ran_arr.shift).clone}
    

    You will notice that it still doesn't generate a matrix with zeroes on the diagonal. To achieve this goal you need to correct the algorithm:

    def create_sym_matrix
      ran_arr = Array.new(5){rand(1..100)}
      ran_arr[4] = 0
      mat_arr = Array.new(5){|i|ran_arr.unshift(ran_arr.pop).clone}
      Matrix.columns(mat_arr)
    end
    

    The output:

    Matrix[[0, 38, 8, 48, 73], [73, 0, 38, 8, 48], [48, 73, 0, 38, 8], [8, 48, 73, 0, 38], [38, 8, 48, 73, 0]]