ruby: как работать с Array без изменения его начальных значений? - PullRequest
0 голосов
/ 08 ноября 2019

У меня был опыт работы с python, и я очень плохо знаком с ruby. Массив действительно смутил меня. Вот мой пример кода:

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]

new_edges = Array.new(edges)
new_edges.map{|edge| edge.map{|point| point.insert(0, 808912)}}

edges
#=> [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]

new_edges
#=> [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]

я верю, что значения в edges не будут изменены, а new_edges будут изменены, однако оба они будут изменены. Как я могу создать копию исходного массива и вести с ним без изменения каких-либо инициалов?

Ответы [ 3 ]

6 голосов
/ 08 ноября 2019

Ваш код имеет те же проблемы, что и в Python:

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = list(edges)
for edge in edges:
    for point in edge:
        point.insert(0, 808912)
print(edges)

Т.е. вы преобразуете внешний массив, но внутренние массивы хранятся только по ссылке, и, таким образом, меняете их (на insert). ) меняется глубокий контент как edges, так и new_edges. Подобные проблемы очень легко понять, пройдя по коду с помощью этого инструмента (несмотря на название, он работает как для Python, так и для Ruby).

В Ruby вместо использования insert, который модифицирует массив, вы можете использовать +, который не:

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
new_edges = edges.map { |edge| edge.map { |point| [808912] + point } }
# => [[[808912, 1, 2], [808912, 100, 200]], [[808912, 700, 400], [808912, 1000, 2000]]]
edges
# => [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]
2 голосов
/ 08 ноября 2019

Вот более общая ситуация, которая иллюстрирует вашу проблему и способы ее устранения. Предположим, у нас были следующие вложенные массивы:

a0 = [1, 2]
a1 = [3, 4]
a = [a0, a1]
  #=> [[1, 2], [3, 4]] 
edges = [a]
  #=> [[[1, 2], [3, 4]]] 

a0, a1, a и edges имеют уникальные идентификаторы объектов:

edges.object_id           #=> 1208140 

a.object_id               #=> 1085620  
edges[0].object_id        #=> 1085620 

a0.object_id              #=> 0977080 
a[0].object_id            #=> 0977080
edges[0][0].object_id     #=> 0977080

a1.object_id              #=> 0995980 
a[1].object_id            #=> 0995980
edges[0][1].object_id     #=> 0995980
edges[0][1][0].object_id  #=> 7

Для удобства чтения у меня естьудалены первые семь цифр каждого из идентификаторов объекта, который во всех случаях равен 4833847. Примечание edges[0][1][0] #=> 3 и 3.object_id #=> 7. Из соображений эффективности целые числа (и некоторые другие объекты Ruby имеют фиксированные, малые идентификаторы объектов.

Теперь создайте новый массив из edges, используя метод Array :: new :

new_edges = Array.new(edges)
  #=> [[[1, 2], [3, 4]]]

Изучите (последние шесть цифр) идентификаторы объекта:

new_edges.object_id          #=> 2400460 (different than edges.object_id)
new_edges[0].object_id       #=> 1085620 (same as edges[0].object_id)
new_edges[0][0].object_id    #=> 0977080 (same as edges[0][0].object_id)
new_edges[0][1].object_id    #=> 0995980 (same as edges[0][1].object_id)
new_edges[0][1][0].object_id #=> 7       (same as edges[0][1][0].object_id)    

Видно, что new_edges - это новый объект, но все его вложенные массивы и элементыте же объекты, что и соответствующие вложенные массивы и элементы в edges.

Теперь давайте сделаем следующее:

edges[0][1][0] = 5
edges[0][1][0].object_id #=> 11

Тогда

edges
  #=> [[[1, 2], [5, 4]]] 
new_edges 
  #=> [[[1, 2], [5, 4]]]

new_edges былоизменилось так же, как edges, потому что edges[0][1] и new_edges[0][1] - это один и тот же объект (массив), и мы только что изменили первый элемент этого объекта.

Как избежать изменения new_edges при edges изменяется?

Во-первых, обратите внимание, что new_edges = Array.new(edges) можно заменить на new_edges = edges.dup. Как и раньше, edges и new_edges будут разными объектами, но все их соответствующие вложенные массивы будут одинаковыми объектами.

Мы хотим определить new_edges, сделав глубокую копию изedges, чтобы изменения в последнем не влияли на первое, и наоборот:

new_edges = edges.map { |a| a.map { |aa| aa.dup } }  
  #=> [[[1, 2], [3, 4]]]
new_edges.object_id       #=> 2134620 (different than edges.object_id) 
new_edges[0].object_id    #=> 2134600 (different than edges[0].object_id)
new_edges[0][0].object_id #=> 2134580 (different than edges[0][0].object_id)
new_edges[0][1].object_id #=> 2134560 (different than edges[0][1].object_id)

Теперь измените вложенный элемент в edges и наблюдайте значения edges и new_edges:

edges[0][1][0] = 5

edges
  #=> [[[1, 2], [5, 4]]] 
new_edges
  #=> [[[1, 2], [3, 4]]]

Видно, что new_edges не изменяется.

Если есть большие уровни вложенности, создание глубокой копии с использованием map и dup может стать утомительным и подверженным ошибкам. Более простой способ - использовать Marshal # dump и Marshal # load , которые создают глубокие копии широкого диапазона объектов Ruby, которые могут содержать несколько уровней вложенных объектов:

edges
  #=> [[[1, 2], [5, 4]]] 
new_edges = Marshal.load(Marshal.dump(edges))
  #=> [[[1, 2], [5, 4]]]

Изменения edges теперь оставят new_edges без изменений.

edges[0][1][0] = 3

edges
  #=> [[[1, 2], [3, 4]]] 
new_edges
  #=> [[[1, 2], [5, 4]]] 
0 голосов
/ 08 ноября 2019

Вы начинаете с

edges = [[[1, 2], [100, 200]], [[700, 400], [1000, 2000]]]

Dup здесь не будет работать, поскольку он является вложенным массивом, поэтому вам нужно выполнить deep_dup, прежде чем делать то, что вы хотите. (Если вы не хотите изменять исходный массив)

Вот пример обезьяньей патчей deep_dup (Array)

  def deep_dup
    new_arr = []
    self.each do |ele|
      if ele.is_a? Array
        new_arr << ele.deep_dup
      else
        new_arr << ele
      end
    end
   new_arr
  end

Вы можете сделать obv. сделать это намного чище, но что бы то ни было

EDIT: Array имеет встроенный метод deep_dup, используйте это. Я всегда думал, что Ruby оставил deep_dup вплоть до реализации, но я ошибался.

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