user6925364
user6925364

Reputation:

Ruby codes works

I have the ff array:

words =  ['demo', 'none', 'tied', 'evil', 'dome', 'mode', 'live',
          'fowl', 'veil', 'wolf', 'diet', 'vile', 'edit', 'tide',
          'flow', 'neon']

And now I am trying to do an anagram like this:

["demo", "dome", "mode"]
["neon", "none"]
(etc)

So I got this code:

result = {}

words.each do |word|
  key = word.split('').sort.join
  if result.has_key?(key)
    result[key].push(word)
  else
    result[key] = [word]
  end
end

result.each do |k, v|
  puts "------"
  p v
end

I understand how the word got split and joined but this part here is not clear to me:

if result.has_key?(key)
    result[key].push(word)
  else
    result[key] = [word]
  end

On the code above it's pretty obvious that result is an empty hash and now we're asking if it has a key of the sorted/joined key via if result.has_key?(key) How does that work? Why ask an empty hash if it has a key of the selected key via word iteration?

result[key].push(word) also is not clear to me. So is this code putting the key inside the result as its key? or the word itself?

result[key] = [word] this one also. Is it adding the word inside the array with the key?

Sorry I am bit confused.

Upvotes: 0

Views: 75

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110725

Considering that you have answers that provide good explanations of your problem, I would like to present some more Ruby-like approaches that could be used. All of these methods create a hash h whose values are arrays of words that are anagrams of each other, which can be extracted from the hash by executing h.values.

Use Enumerable#group_by and Array#sort

This is arguably the most direct approach.

words.group_by { |w| w.each_char.sort }.values
  #=> [["demo", "dome", "mode"], ["none", "neon"], ["tied", "diet", "edit", "tide"],
  #    ["evil", "live", "veil", "vile"], ["fowl", "wolf", "flow"]]

group_by produces

words.group_by { |w| w.each_char.sort }
  #=> {["d", "e", "m", "o"]=>["demo", "dome", "mode"],
  #    ["e", "n", "n", "o"]=>["none", "neon"],
  #    ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"],
  #    ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"],
  #    ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]}

after which it is a simple matter of extracting the values of this hash.

Build a hash by appending words to arrays that are the values of the hash

words.each_with_object({}) { |w,h| (h[w.each_char.sort] ||= []) << w }.values
  #=> [["demo", "dome", "mode"], ["none", "neon"], ["tied", "diet", "edit", "tide"],
  #    ["evil", "live", "veil", "vile"], ["fowl", "wolf", "flow"]]

When "demo" is passed to the block the hash h is empty, so the block variables are assigned values

w = "demo"
h =  {}  

and the block calculation is performed:

h[["d", "e", "m", "o"]] ||= []) << w

as

w.each_char.sort
  #=> ["d", "e", "m", "o"]

Ruby first expands this to

h[["d", "e", "m", "o"]] = (h[["d", "e", "m", "o"]] ||= []) << "demo" 

At this point h has no keys, so h[["d", "e", "m", "o"]] evaluates to nil. The expression therefore becomes

h[["d", "e", "m", "o"]] = (nil ||= []) << "demo" 
                        = [] << "demo"
                        = ["demo"]

Later, when "dome" is encountered,

w = "dome"
w.each_char.sort
  #=> ["d", "e", "m", "o"]

and since h already has this key, the block calculation is as follows.

h[["d", "e", "m", "o"]] = (h[["d", "e", "m", "o"]] ||= []) << "dome" 
                        = (["demo"] ||= []) << "dome"
                        = ["demo"] << "dome"
                        = ["demo", "dome"]

We obtain

words.each_with_object({}) { |w,h| (h[w.each_char.sort] ||= []) << w }
  #=> {["d", "e", "m", "o"]=>["demo", "dome", "mode"],
  #    ["e", "n", "n", "o"]=>["none", "neon"],
  #    ["d", "e", "i", "t"]=>["tied", "diet", "edit", "tide"],
  #    ["e", "i", "l", "v"]=>["evil", "live", "veil", "vile"],
  #    ["f", "l", "o", "w"]=>["fowl", "wolf", "flow"]} 

after which the values are extracted.

A variant of this is the following.

words.each_with_object(Hash.new { |h,k| h[k] = []}) { |w,h|
  h[w.each_char.sort] << w }.values

See the doc for Hash::new for an explanation, in particular the discussion of default values given by a block.

For each word, merge a hash having a single key into an initially-empty hash

words.each_with_object({}) { |w,h|
  h.update(w.each_char.sort=>[w]) { |_,o,n| o+n } }.values

The argument w.each_char.sort=>[w] is shorthand for { w.each_char.sort=>[w] }.

This uses the form of Hash#update (aka merge!) that employs a "resolution" block (here { |_,o,n| o+n }) to determine the values of keys that are present in both hashes begin merged. See the doc for a description of that block's three keys (the first block variable, the common key, is not used in this calculation, which is why I used an underscore).

Upvotes: 0

the Tin Man
the Tin Man

Reputation: 160601

There are tools that help you figure this stuff out on your own. Here's an example using Seeing Is Believing with vim:

words =  ['demo', 'mode']

result = {}

words.each do |word| # => ["demo", "mode"]
  key = word # => "demo", "mode"
        .split('') # => ["d", "e", "m", "o"], ["m", "o", "d", "e"]
        .sort # => ["d", "e", "m", "o"], ["d", "e", "m", "o"]
        .join # => "demo", "demo"
  result  # => {}, {"demo"=>["demo"]}
  if result.has_key?(key)
    result[key].push(word) # => ["demo", "mode"]
  else
    result[key] = [word] # => ["demo"]
  end
  result  # => {"demo"=>["demo"]}, {"demo"=>["demo", "mode"]}
end

result.each do |k, v| # => {"demo"=>["demo", "mode"]}
  puts "------"
  p v
end

# >> ------
# >> ["demo", "mode"]

Other tools I'd use are Irb and Pry.

Upvotes: 1

jonhopkins
jonhopkins

Reputation: 3842

The results is only empty on the first iteration of the loop. The line

if result.has_key?(key)

is checking if the key created by sorting the letters in the current word exists, and in the case of the first iteration when it's empty, yes, it is obviously not there this time, but it still needs to check every other time too.

Now, when a particular key does not exist yet in results, that key is added to results and a new array containing the current word is added as the value for that key, in the line

result[key] = [word]

When a key already exists in results, that means there is already an array containing at least one word, so the current word is added into that array, in the line

result[key].push(word)

Stepping through what's happening:

words = ['demo', 'neon', 'dome', 'mode', 'none']

// first iteration of the loop
word = 'demo'
key = 'demo' // the letters in "demo" happen to already be sorted
Is 'demo' a key in results?
results is currently {}
No, 'demo' is not a key in {}
Add 'demo' as a key, and add an array with 'demo' inside
results is now { 'demo' => ['demo'] }

// second iteration
word = 'neon'
key = 'enno'
Is 'enno' a key in results?
results is currently { 'demo' => ['demo'] }
No, 'enno' is not a key in { 'demo' => ['demo'] }
Add 'enno' as a key, and add an array with 'neon' inside
results is now { 'demo' => ['demo'], 'enno' => ['neon'] }

// third iteration
word = 'dome'
key = 'demo'
Is 'demo' a key in results?
results is currently { 'demo' => ['demo'], 'enno' => ['neon'] }
Yes, 'demo' is a key in { 'demo' => ['demo'], 'enno' => ['neon'] }
Add 'dome' to the array at key = 'demo'
results is now { 'demo' => ['demo', 'dome'], 'enno' => ['neon'] }

// ... and so on

Upvotes: 4

Related Questions