Почему поведение array.each зависит от синтаксиса Array.new? - PullRequest
6 голосов
/ 27 января 2012

Я использую Ruby 1.9.2-p290 и обнаружил:

a = Array.new(2, []).each {|i| i.push("a")}    
=> [["a", "a"], ["a", "a"]]

Это не то, что я ожидал.Но следующий стиль конструктора делает то, что я ожидал:

b = Array.new(2) {Array.new}.each {|i| i.push("b")}
=> [["b"], ["b"]] 

Первый пример - ожидаемое поведение?

В ruby-doc похоже, что мой аргумент size=2 такой жесвоего рода аргумент для обоих конструкторов.Я думаю, что если метод each получает этот аргумент, он будет использовать его одинаково для обоих конструкторов.

Ответы [ 3 ]

10 голосов
/ 27 января 2012

Это распространенное недоразумение. В первом примере вы создаете массив из 2 элементов. Оба указателя являются одинаковыми массивом . Итак, когда вы выполняете итерацию по внешнему массиву, вы добавляете 2 элемента во внутренний массив, что затем отражается в ваших выходных данных дважды

Сравните это:

> array = Array.new(5, [])
=> [[], [], [], [], []] 

# Note - 5 identical object IDs (memory locations)
> array.map { |o| o.object_id }
=> [70228709214620, 70228709214620, 70228709214620, 70228709214620, 70228709214620] 

> array = Array.new(5) { [] }
=> [[], [], [], [], []] 

# Note - 5 different object IDs (memory locations)
> array.map { |o| o.object_id }
=> [70228709185900, 70228709185880, 70228709185860, 70228709185840, 70228709185780] 
4 голосов
/ 27 января 2012

В первом случае вы используете один экземпляр Array в качестве значения по умолчанию для элементов основного массива:

a = Array.new(2, []).each {|i| i.push("a")}

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

Второй способ - правильный способ сделать это:

b = Array.new(2) {Array.new}.each {|i| i.push("b")

Это намеренно создает новый экземпляр массива для каждой позиции в основном массиве.Важным отличием здесь является использование блока { ... }, который выполняется один раз для каждой позиции в новом массиве.Краткая версия этого будет:

b = Array.new(2) { [ ] }.each {|i| i.push("b")
1 голос
/ 27 января 2012

Из рубиновой документации:

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

Возвращает новый массив. В первой форме новый массив пуст. Во втором он создается с копиями размера объекта obj (то есть с указанием размера одного и того же объекта obj). Третья форма создает копию массива, переданного в качестве параметра (массив генерируется путем вызова to_ary для параметра). В последней форме создается массив заданного размера.

Таким образом, в массиве a, который вы создаете, у вас есть две ссылки на один и тот же массив, поэтому push работает с ними обоими. То есть вы дважды нажимаете "a" на один и тот же массив. В массиве b, который вы создаете, вы фактически создаете новый массив для каждого элемента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...