0

I have:

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

I want to get:

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

I did:

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

I get:

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

I couldn't figure out why adding new key/value pair with unique key would overwrite existing pair in a hash. Any help with explanation would be greatly appreciated.

sawa
  • 165,429
  • 45
  • 277
  • 381
Jaysan
  • 43
  • 2
  • 4
  • 2
    As you've already received your answer, your method can also be rewritten to: `fruits.group_by { |k,v| v[:season] }.transform_values {|v| v.map(&:first) }` – Marcin Kołodziej Oct 14 '18 at 19:57

2 Answers2

1

Your problem is that you reuse the same fruits array for all the values. Even though you clear it, is is still the same array. If instead of fruits.clear you use fruits = [] then you won't have the issue.

You can see the issue in the following as an example:

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

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

You could alternatively use sorted[season] = fruits.clone or sorted[season] = [*fruits] ... anything that uses a new array.

You have to keep track of when you use 'mutation' methods (those that change objects in-place such as clear) - this is a common pitfall when working with hashes and arrays

max pleaner
  • 26,189
  • 9
  • 66
  • 118
1

In Ruby mutable objects are passed by reference. It means that when you iterate over seasons in each block this line:

sorted[season] = fruits

saves to sorted[season] a reference to fruits, for every season. After each loop finishes every season has a reference to the same fruits array, which contain items calculated on the last step of the iterator. In your case, it's ["grape"].

Ilya Konyukhov
  • 2,666
  • 1
  • 12
  • 21
  • Thanks llya for the explanation! – Jaysan Oct 14 '18 at 20:45
  • See [this](http://www.pythontutor.com/visualize.html#code=a%20%3D%20%7B%20num%3A%201%20%7D%0Ab%20%3D%20%5B%5D%0A3.times%20do%0A%20%20b.push%28a%29%0A%20%20a%5B%3Anum%5D%20%2B%3D%201%0Aend%0Aputs%20b&cumulative=false&heapPrimitives=nevernest&mode=edit&origin=opt-frontend.js&py=ruby&rawInputLstJSON=%5B%5D&textReferences=false) for a vivid demo (click Visualize Execution, then keep clicking Forward to progress). – Amadan Oct 15 '18 at 04:47
  • Technically speaking ruby is strictly pass by value.https://stackoverflow.com/questions/22827566/ruby-parameters-by-reference-or-by-value/22827949#22827949 – engineersmnky Oct 15 '18 at 15:21
  • ```It's pass-by-value, but all the values are references```. So it's more a question of terminology that can be confusing. – Ilya Konyukhov Oct 15 '18 at 15:34