Следующая пара ключ/значение перезаписывает существующую пару в хеше при попытке добавить пару с новым ключом

У меня есть:

fruits = {
  "orange" => {:season => "winter"},
  "apple" => {:season => "winter"},
  "banana" => {:season => "summer"},
  "grape" => {:season => "spring"},
  "peach" => {:season => "winter"},
  "pineapple" => {:season => "summer"}
}

Я хочу получить:

{
  "winter"=>["orange", "apple", "peach"],
  "summer"=>["banana", "pineapple"],
  "spring"=>["grape"]
}

Я сделал:

def sort_fruits(fruits_hash)
  fruits=[]
  sorted = {}
  seasons = fruits_hash.map {|k, v|v[:season]}
  seasons.uniq.each do |season|
    fruits.clear
    fruits_hash.each do |fruit, season_name|
      if season == season_name[:season]
        fruits << fruit
      end
    end
    p sorted[season] = fruits ## season changes to new season, so this should have created new key/value pair for new season.
  end
  sorted
end

Я получил:

{
  "winter"=>["grape"],
  "summer"=>["grape"],
  "spring"=>["grape"]
}

Я не мог понять, почему добавление новой пары ключ/значение с уникальным ключом перезаписывает существующую пару в хеше. Любая помощь с объяснением будет принята с благодарностью.


person Jaysan    schedule 14.10.2018    source источник
comment
Поскольку вы уже получили свой ответ, ваш метод также можно переписать на: fruits.group_by { |k,v| v[:season] }.transform_values {|v| v.map(&:first) }   -  person Marcin Kołodziej    schedule 14.10.2018


Ответы (2)


В Ruby изменяемые объекты передаются по ссылке. Это означает, что когда вы перебираете seasons в each, блокируйте эту строку:

sorted[season] = fruits

сохраняет в sorted[season] ссылку на fruits для каждого сезона. После завершения цикла each каждый сезон имеет ссылку на один и тот же массив fruits, содержащий элементы, вычисленные на последнем шаге итератора. В вашем случае это ["grape"].

person Ilya Konyukhov    schedule 14.10.2018
comment
Илья спасибо за объяснение! - person Jaysan; 14.10.2018
comment
См. это для яркой демонстрации (нажмите «Визуализировать выполнение», затем продолжайте нажимать «Вперед», чтобы продолжить). - person Amadan; 15.10.2018
comment
С технической точки зрения ruby ​​строго передается по значению. по ссылке или по значению">stackoverflow.com/questions/22827566/ - person engineersmnky; 15.10.2018
comment
It's pass-by-value, but all the values are references. Так что это скорее вопрос терминологии, которая может сбивать с толку. - person Ilya Konyukhov; 15.10.2018

Ваша проблема в том, что вы повторно используете один и тот же массив fruits для всех значений. Даже если вы очистите его, это все тот же массив. Если вместо fruits.clear вы используете fruits = [], у вас не будет проблемы.

Вы можете увидеть проблему в следующем примере:

arr = ['val']
hash = {
  key1: arr,
  key2: arr
}
p hash # => { key1: ['val'], key2: ['val'] }

arr.clear
p hash # => { key1: [], key2: [] }

В качестве альтернативы вы можете использовать sorted[season] = fruits.clone или sorted[season] = [*fruits]... все, что использует новый массив.

Вы должны отслеживать, когда вы используете методы «мутации» (те, которые изменяют объекты на месте, такие как clear) — это распространенная ошибка при работе с хэшами и массивами.

person max pleaner    schedule 14.10.2018
comment
Спасибо макс. Объяснение и демонстрация были очень полезны для начинающих, таких как я. - person Jaysan; 14.10.2018