2015-07-02 4 views
1

Я пытаюсь изменить копию массива без изменения исходного массива. Это массив хэшей, так, чтобы сделать «все новое» копию массива я использую:Почему действие << b действует иначе, чем a = a + b (с скопированным массивом хэшей со строками)

foo = [ { :a => "aaaaaa" } ] 
foocopy = foo.map { |h| h.dup } 

Я хочу добавить некоторые данные в виде строки в хэш в копии.

Он отлично работает, если я использую = и +:

foocopy.first[:a] = foocopy.first[:a] + "bbbbb" 
foo 
=> [{:a=>"aaaaaa"}] # original unchanged as expected 
foocopy 
=> [{:a=>"aaaaaabbbbb"}] 

Однако, если я использую << это модифицированный ОБА копию и оригинал:

foocopy.first[:a] << "cccccc" 
foo 
=> [{:a=>"aaaaaacccccc"}] # ORIGINAL got changed too 
foocopy 
=> [{:a=>"aaaaaacccccc"}] 

Это ошибка в Ruby?

+0

Это очень * очень маловероятно, что вы найдете ошибку в хорошо избитых вещах, таких как '<<' и '+' при работе с массивами. –

+0

И все же ... это происходит ... http://stackoverflow.com/questions/29224421/rails-3-2-saving-serialized-hash-will-not-save-number-with-delimiter, который до любой, кто взвесил, кажется, является ошибкой рельсов, которая сохраняется до 4. В этом случае из-за конкретного случая (изменение строки внутри хэша внутри скопированного массива) казалось правдоподобным быть ошибкой. Но объяснение, данное @jorge, было поучительным. Моя точка зрения, хотя и маловероятна, это не невозможно, и AFAIK SO - довольно хорошее место, чтобы спросить и узнать. – jpwynn

+1

Рельсы - это едва ли тот же слой, что и Ruby, а '<<' и '+' используются чрезмерно в несколько раз больше, чем метод Rails *, любой метод * Rails. Таким образом, поиск ошибки в Rails гораздо более вероятен, чем основной метод в Ruby. –

ответ

2

dup выполняет "неполную копию" объекта. Таким образом, вы создаете новый хэш, который имеет такие же ключи и значения! К сожалению, Ruby не имеет приятного встроенного способа создания «глубокой копии» хэша, где также копируются все объекты, на которые ссылаются. Итак, что нужно делать?

Я думаю, вы уже нашли лучшее решение, которое должно использовать +=. Это потому, что + создает новый объект, а = перезаписывает скопированный объект.

Но есть простой взлом для глубокой копирования объекта в Ruby, который должен сериализовать/неэтериализовать его, используя Marshal.

foo = [ { :a => "aaaaaa" } ] 
foocopy = Marshal.load(Marshal.dump(foo)) 

Тогда у вас не будет никаких сюрпризов из-за того, что указатели разделяются между объектами. И ваш код << будет работать так, как вы ожидали.

+0

Маршал - отличный способ справиться с этой проблемой. –

2

Нет, это связано с тем, что вы дублировали массив и хеш, но строка представляет собой объект с тем же идентификатором, что и рубин, который управляет строками странным образом.

irb(main):001:0> foo = [ { :a => "aaaaaa" } ] 
=> [{:a=>"aaaaaa"}] 
irb(main):002:0> foocopy = foo.map { |h| h.dup } 
=> [{:a=>"aaaaaa"}] 
irb(main):003:0> foo.object_id 
=> 70252221980900 
irb(main):004:0> foocopy.object_id 
=> 70252221915920 
irb(main):005:0> foocopy.first.object_id 
=> 70252221915880 
irb(main):006:0> foo.first.object_id 
=> 70252221980940 
irb(main):007:0> foocopy.first[:a].object_id 
=> 70252221980960 
irb(main):008:0> foo.first[:a].object_id 
=> 70252221980960 

Это означает, что: a+b reinstantiates этот объект во что-то изменилось, и a << b изменяет экземпляр объекта. Это фактическое поведение метода.

Просто со строкой:

irb(main):009:0> a = "test" 
=> "test" 
irb(main):010:0> b = a.dup 
=> "test" 
irb(main):011:0> a.object_id 
=> 70252221685660 
irb(main):012:0> b.object_id 
=> 70252221662100 
irb(main):013:0> a = a + "1" 
=> "test1" 
irb(main):014:0> a.object_id 
=> 70252221586140 
irb(main):015:0> b << "1" 
=> "test1" 
irb(main):016:0> b.object_id 
=> 70252221662100 

А из документации:

http://ruby-doc.org/core-2.2.0/String.html#method-i-2B

http://ruby-doc.org/core-2.2.0/String.html#method-i-3C-3C

 Смежные вопросы

  • Нет связанных вопросов^_^