Reputation:
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
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
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
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