2016-08-13 2 views
0

Я использую Ruby 2.3.1, и я не могу сказать, встречался ли я с ошибкой или если это предполагаемое поведение.Нечеткое поведение с массивами Ruby?

Если создать матрицу NxN, делая вложенные массивы, как например:

matrix = [[0]*5]*5 

, а затем установите элементы на диагоналях, как например:

(0..4).each {|i| matrix[i][i] = i} 

это заканчивается влияет на каждый столбец в каждом ряду:

[ 
[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] 
] 

Это намеренное поведение?

P.S. Я не хочу использовать библиотеку Matrix Ruby, но скорее буду работать с обычными массивами.

Заранее спасибо :)

+0

Итак, это правильный способ сделать это: 'Array.new (n) {Array.new (n, 0)}', но не совсем уверен в деталях – DaniG2k

+5

«Я нашел ошибку в компиляторе/интерпретатор/независимо »- это почти всегда ложно :) –

+0

@ Серхио, это потому, что они обычно являются аппаратными ошибками. –

ответ

1

В Ruby массивы являются за кулисами объектов типа array, которые могут содержать примитивные типы и ссылки на другие объекты. Теперь этот последний бит важен - массив не содержит самого объекта, а вместо него указатель на него, который интерпретируется как необходимый, когда программист просит его.

Так исходный код инициализации OP в

matrix = [[0]*5]*5 

Действительно создает один array объект, содержащий 5 0s, а затем копирует указатель на него в 5 раз. Это также происходит, когда вы делаете

matrix = Array.new(5, Array.new(5, 0)) 

по той же причине. Так что, как писал в комментариях, то идиоматически правильный путь рубин, чтобы создать массив из 5 различных array объектов

matrix = Array.new(5){Array.new(5, 0)} 

Который дает единый массив, который содержит указатели на 5 различныхarray объектов, предотвращая Возникшая по ОП. Полную документацию о поведении массивов Ruby можно найти на этом finely-crafted link.

0
matrix = [[0]*5]*5 

Давайте сломаем это вниз:

a = [0]*5 

Создать массив из 5 нулей; это массив целых чисел.

matrix = [a] * 5 

Создать массив из 5 ссылок в том же массиве a.

Так что, конечно, если вы измените его, остальные будут изменены; это тот же массив.

Я не знаю Ruby, поэтому, пожалуйста, не стесняйтесь исправить любую неверную терминологию.

+0

Да это кажется правильно. Странно, что это происходит как с [[0] * 5] * 5', так и с Array.new (5, Array.new (5, 0)) ', где я ожидал бы двух разных массивов, но на самом деле они ссылку на тот же объект. Правильный путь, кажется, передает блок, как таковой: 'Array.new (n) {Array.new (n, 0)}' – DaniG2k

1

Вам не нужно менять диагональ, чтобы наблюдать за этим поведением; просто изменить любой элемент, скажем

matrix[1][1] = 1 

Тогда

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]] 

Рассмотрим

matrix.map { |row| row.object_id } 
    #=> [70153694327100, 70153694327100, 70153694327100, 
    # 70153694327100, 70153694327100]. 

Это показывает, что все элементы ("ряды") из matrix являются тем же объектом, следовательно, если объект изменяется, все элементы matrix затронуты. matrix = [[0]*5]*5 эквивалентно

matrix = Array.new(5, Array.new(5,0)) 

(См Array::new, EXPECIALLY "распространённые ошибки".) То, что вы хотите (как @Sebastian примечания) является

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]] 

так что

matrix[1][1] = 1 

влияет только на что один элемент:

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]] 
Смежные вопросы