2016-03-28 3 views
3

У меня есть следующий сценарий на Ruby:Толчок Hash на массив: последний Hash перезаписи предыдущих элементов массива

arr = ['bob', 'jack', 'smith'] 
array_of_hashes = Array.new 
hash = Hash.new 

arr.each do |item| 
    hash.clear 
    hash[:name] = item 
    array_of_hashes << hash 
end 

puts array_of_hashes 

Это возвращает массив хэш, чей: ключи имени все от последнего элемента.

[ 
    [0] { 
     :name => "smith" 
    }, 
    [1] { 
     :name => "smith" 
    }, 
    [2] { 
     :name => "smith" 
    } 
] 

Я бы ожидать, что она возвращает следующее, но я застрял, пытаясь выяснить, почему элемент последний Хэш перезаписывать все предыдущие элементы массива:

[ 
    [0] { 
     :name => "bob" 
    }, 
    [1] { 
     :name => "jack" 
    }, 
    [2] { 
     :name => "smith" 
    } 
] 

EDIT: Спасибо всем за ваши ответы. Приятно иметь несколько разных методов, выполненных для достижения того же. Я тестировал каждое решение, и каждый из них великолепный. Я закончил тем, что был наиболее похож на мой оригинальный код; но мой вариант использования - простой, локальный скрипт - он не используется в промышленном приложении - в этом случае я бы выбрал другое решение.

ответ

3

Вы должны попробовать что-то вроде

arr = ['bob', 'jack', 'smith'] 
array_of_hashes = Array.new 

arr.each { |item| 
    hash = Hash.new 
    hash[:name] = item 
    array_of_hashes << hash 
} 

puts array_of_hashes 

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

+0

Спасибо за ваш пост Ed; Мне очень нравится, как этот код наглядно иллюстрирует недостаток в моем исходном коде - то есть, у меня только один хэш, с которым я имел дело, и этот код создает новый хэш для каждого элемента массива. – singularity

+0

Я рад, что это помогло, @singularity. Спасибо за поддержку. –

+0

Я принял этот ответ сейчас, потому что это то, что я закончил делать. Это был самый быстрый путь от моего исходного кода к рабочему скрипту. По сути, 'Hash.new' должен был находиться в блоке' arr.each'. '.clear' тоже не нужен. Хотя это может быть не самое элегантное решение, это наиболее практично для моего сценария и выполняет свою работу. – singularity

5

Посмотрите на свой код внимательно, вы увидите, что вы создали только один экземпляр Hash. Ваш итератор продолжает сдувать предыдущие усилия с .clear, устанавливая элемент в текущую итерацию и добавляя ссылку на этот объект Hash в разные местоположения массива в array_of_hashes. Но в конце дня все они указывают на тот же объект Hash, и что объект Hash содержит только последнее, что вы положили в него.

4

Примите во внимание (и понимайте) ответ @pjs и комментарий @tadman, поскольку они объясняют, почему вы получаете результаты, которые получаете.

Я бы на самом деле вместо того, чтобы сделать что-то вроде:

names = ['bob', 'jack', 'smith'] 

# combine the results into an array of Hashes 
array_of_hashes = 
    names.collect do |item| 
    { name: item } 
    end 

# output the results 
array_of_hashes # [{name: "bob"}, {name: "jack"},{name:"smith"}] 

Некоторые замечания:

  • Метод Array#collect возвращает массив, так что вам не нужно вручную добавить в массив вас» вы инициализировали себя.
  • Вы можете просто вернуть новый Хеш для каждого элемента, который будет возвращен в collect блоке
+2

Ключ здесь состоит в том, что 'массив << hash' толкает * объектную ссылку * на' hash' в массив, а не копию. – tadman

+0

Это похоже на самый «рубиновый» способ обойти это. – singularity

+0

Протестируйте код без «ставит» и посмотрите ... он не показывает никаких результатов, извините. Вот почему я отредактировал ваш ответ. Попробуйте, затем отредактируйте его самостоятельно. –

2

Я хотел бы написать

[:name].product(arr).map { |pair| Hash[[pair]] } 
    #=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}] 

или (для Ruby, v2.0+)

[:name].product(arr).map { |pair| [pair].to_h } 

шаги:

a = [:name].product(arr) 
    #=> [[:name, "bob"], [:name, "jack"], [:name, "smith"]] 
enum = a.map 
    # => #<Enumerator: [[:name, "bob"], [:name, "jack"], [:name, "smith"]]:map> 

Обратите внимание, что, посылая метод Enumerator#each к enum мы получаем искомый результат:

enum.each { |pair| [pair].to_h } 
    #=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}] 

(Enumerator#eachArray#each будет вызывать, потому что a.class #=> Array.)

Enumerator#each устанавливает переменную блока к первому элементу enum:

pair = enum.next 
    #=> [:name, "bob"] 

и расчет блока выполняется:

[pair].to_h 
    #=> [[:name, "bob"]].to_h 
    #=> {:name=>"bob"} 

Далее, второй элемент enum, [:name, "jack"], сопоставляется:

pair = enum.next 
    #=> [:name, "jack"] 
[pair].to_h 
    #=> [[:name, "jack"]].to_h 
    #=> {:name=>"jack"} 

И, наконец,

pair = enum.next 
    #=> [:name, "smith"] 
[pair].to_h 
    #=> [[:name, "smith"]].to_h 
    #=> {:name=>"smith"} 
+0

Это кажется элегантным. Это немного над моей головой, но для n00bs (включая меня), чтобы увидеть результат, вы можете «поставить» это или назначить его переменной var - 'hash = [: name] .product (arr) .map {| pair | [пара] .to_h} ' – singularity

+0

Я расскажу утром. Это менее сложно, чем может показаться на первый взгляд. –

+0

Ничего себе, спасибо за разработку! Это помогает немного ослабить шаги – singularity

0

Вы можете попробовать это слишком

>>> names = ['bob', 'jack', 'smith'] 
>>> names.inject([]){|s,p| s << {name: p}} 
=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}] 
Смежные вопросы