Array.each иногда дает непредсказуемые результаты при использовании для изменения элемента массива - PullRequest
1 голос
/ 14 сентября 2011

Я отлаживал проблему в приложении RoR и наткнулся на приведенный ниже код (где «map» - двумерный массив целых чисел).Код пытается дублировать и добавить последний элемент каждого подмассива:

map.each { |x| x << x[-1] }

До этой строки кода,

    (rdb:29) p map
    [[1, 2, 1, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4],
    [1, 2, 2, 3, 4], [1, 2, 2, 3, 4]]
    (rdb:29) p map.class
    Array
    (rdb:29) p map.first.class
    Array
    (rdb:29) p map.last.class
    Array

После:

    (rdb:29) p map
    [[1, 2, 1, 3, 4, 4], [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4],
    [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4, 4], [1, 2, 2, 3, 4, 4, 4]]

Проблема здесь в том, что последние 2 подмассива были добавлены с двумя целыми числами вместо одного, в то время как это правильно для первых 4 подмассивов.Вместо этого я изменил код для использования Array.map, тогда он работал правильно:

map = map.map { |x| x + [x[-1]] }

Подводя итог: Я знаю, что элемент итерации в блоке Array.each не предполагаетсябыть измененным.Но почему это дает непредсказуемые результаты при этом?Код на самом деле работает большую часть времени, проблема была замечена иногда.Была ли это ошибка в Ruby или RoR?

1 Ответ

2 голосов
/ 14 сентября 2011

Изменение подмассива в блоке each не является вашей проблемой, в этом нет ничего плохого. Ваша проблема в том, что ваш внешний массив map иногда содержит несколько ссылок на один и тот же объект подмассива.

Учтите это:

>> x = [[1, 2, 1, 3, 4], [1, 2, 2, 3, 4], [1, 2, 2, 3, 4]]
>> x.each { |a| a << a[-1] }
=> [[1, 2, 1, 3, 4, 4], [1, 2, 2, 3, 4, 4], [1, 2, 2, 3, 4, 4]]

Результаты будут одинаковыми каждый раз. Но если у вас есть это:

>> gotcha = [1, 2, 1, 3, 4]
>> x = [[1, 2, 1, 3, 4], gotcha, gotcha]
>> x.each { |a| a << a[-1] }
=> [[1, 2, 1, 3, 4, 4], [1, 2, 1, 3, 4, 4, 4], [1, 2, 1, 3, 4, 4, 4]]

Тогда вы получите дополнительные конечные элементы, которые вы видели (каждый раз), потому что gotcha изменяется дважды. x во втором случае будет puts таким же, как x в первом случае, но они не совпадают.

Ваш Array#map подход всегда работает, потому что это:

x + [x[-1]]

по существу копирует x, а затем добавляет x[-1] к этой копии, он вообще никогда не изменяет x, поэтому поведение gotcha сверху не произойдет.

Вы не можете уйти от указателей, даже когда они называются ссылками.

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