1

I am trying to create a hash with values as arrays. I am adding elements to those arrays, but for some reason, the hash becomes empty after it runs. I'm not sure why at all. Here is my code

def function(words)
  hash = Hash.new([])  # default value of empty list
  words.each do |word|
    sorted = word.chars.sort.join  # sort the string
    hash[sorted] << word
    ## hash becomes empty here
  end
  return hash
end

puts function ['cars', 'for', 'potatoes', 'racs', 'four']

I'm new to Ruby and I have no idea why the hash is emptying it self. I've written a similar algorithm with the same logic in Python and it works absolutely fine. Any suggestions?

darksky
  • 20,411
  • 61
  • 165
  • 254

2 Answers2

1

The problem is the default hash value you give is a mutable value (partially, at least—see below), you need to instead use Hash.new’s block parameter:

hash = Hash.new { |h, k| h[k] = [] }

You get the correct result:

function ['cars', 'for', 'potatoes', 'racs', 'four']
#=> {"acrs"=>["cars", "racs"],
#    "for"=>["for"],
#    "aeoopstt"=>["potatoes"],
#    "foru"=>["four"]}

The problem with what you have is that hash[sorted] returns [], but never actually assigns to it. So you change the array, but never put it in the hash. If you use += instead (leaving your Hash.new([]), you can that this also works:

hash[sorted] += [words]
Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
-2

Assign Values to Hash Keys With =

You can fix your code easily by replacing hash[sorted] << word with the following:

hash[sorted] = word

That's the only change you actually needed to make your original code work. Consider:

def function(words)
  hash = Hash.new([])
  words.each do |word|
    sorted = word.chars.sort.join
    hash[sorted] = word
  end
  return hash
end

which returns:

function %w[cars for potatoes racs four]
# => {"acrs"=>"racs", "for"=>"for", "aeoopstt"=>"potatoes", "foru"=>"four"}

almost as you expected. In your posted example, word is a string, not an array. If you don't want to just fix the assignment, but to also cast the hash values as arrays, see the suggested refactoring below.

Your Code Refactored

There are a number of ways you can clean this up your original method. For example, here's one way to refactor your code so that it's more idiomatic and easier to read.

def sort_words words
  hash = {}
  words.map { |word| hash[word.chars.sort.join] = Array(word) }
  hash
end

sort_words %w[cars for potatoes racs four]
# => {"acrs"=>["racs"], "for"=>["for"], "aeoopstt"=>["potatoes"], "foru"=>["four"]}

Other refactorings are certainly possible---Ruby is a very flexible and expressive language---but this should get you started on the right path.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • The value of the key in the has is an array and I'd like to append to that array. How can I do so? I looked online and I saw that doing `<<` would append it to the hash's key's value (which is an array). If I run it quickly on `irb` with one value it works. Try `hash = Hash.new([]); hash[1] << "hello"; hash[1] << "bye"` in `irb`. key `1` in the hash will have a value of `["hello", "key"]` – darksky Jan 20 '13 at 06:22
  • 1
    This is wrong, the OP is not calling `<<` on the hash, but the value stored at `hash[sorted]`. – Andrew Marshall Jan 20 '13 at 06:26
  • @Darksky I've updated the answer to address your requirement to cast the hash values as arrays, and provided a possible refactoring to make the *intent* of your code clearer. Good luck with your project! – Todd A. Jacobs Jan 20 '13 at 07:26
  • @AndrewMarshall Doesn't matter; `<<` is still the incorrect operator. Consider `h = Hash.new; h['foo'] << 'bar'`, which raises `NoMethodError: undefined method '<<' for nil:NilClass`. However, `h = Hash.new; h['foo'] = 'bar'` works just fine. – Todd A. Jacobs Jan 20 '13 at 07:40
  • @CodeGnome Except the OP didn’t call `Hash.new`, they called `Hash.new([])` which means `h['foo']` (or any call to `[]` on `h`) will return an array by default, which *does* respond to `<<`. – Andrew Marshall Jan 20 '13 at 16:03