I'm working on a Ruby verison of RSG and somehow stuck on the sentence generating process (...)
so I managed to implement all functions like read, convert to hash...,etc. But problem is how to randomly pick values in hash to generate the sentence?
Now I have a hash here:
hash = {"<start>"=>[["The", "<object>", "<verb>", "tonight."]],
"<object>"=>[["waves"], ["big", "yellow", "flowers"], ["slugs"]],
"<verb>"=>[["sigh", "<adverb>"], ["portend", "like", "<object>"],["die", "<adverb>"]],
"<adverb>"=>[["warily"], ["grumpily"]]}
The Objective is to assemble the strings using random values in the hash for example when the function iterates to "<object>"
, it will take it as a key and search in the hash to find matching key then randomly pick the corresponding values (one of the strings in "<object>"=>[["waves"], ["big", "yellow", "flowers"], ["slugs"]]
) and assemble it, then the output would be something like this:
"The waves sigh warily tonight"
My code so far:
hash.each do |_, value|
value.map{|x| x.map{|y| is_non_terminal?(y) ? (puts value.values_at("SomethingToPutInto")) : (puts y)}}
end
Somehow the logics in the code becomes too complicated and I'm stuck on this step.. Using values_at
will cause TypeError: no implicit conversion of String into Integer (TypeError)
For is_non_terminal?(y)
is just a function to check whether the string contains <
and >
:
def is_non_terminal?(s)
s.include?('<' && '>') ? true : false
end
I assume they're looking for a recursive method, let's call it generate
.
def generate(key)
Read the hash at the key and take one randomly using sample
:
words = @hash[key].sample
Then, for each word, check to see if it's a <key>
. If so, call generate
on it, otherwise save it:
if (word.start_with?("<") && word.end_with?(">"))
generate(word)
else
@sentence << word
end
Putting it all together:
@hash = {"<start>"=>[["The", "<object>", "<verb>", "tonight."]],
"<object>"=>[["waves"], ["big", "yellow", "flowers"], ["slugs"]],
"<verb>"=>[["sigh", "<adverb>"], ["portend", "like", "<object>"],["die", "<adverb>"]],
"<adverb>"=>[["warily"], ["grumpily"]]}
@sentence = []
def generate(key)
words = @hash[key].sample
words.each do |word|
if (word.start_with?("<") && word.end_with?(">"))
generate(word)
else
@sentence << word
end
end
end
generate("<start>")
puts @sentence.join(" ")
Notice I used @-variables to make their scope reachable from within the method.
Sample output: The big yellow flowers sigh grumpily tonight.
Code
def generate(hash, start_key)
mod_hash = hash.transform_values{ |v| v.map { |a| a.join(' ') } }
sentence = mod_hash[start_key].sample
while sentence.include?('<')
sentence.gsub!(/\<.+?\>/) { |s| mod_hash[s].sample }
end
sentence
end
Examples
hash = { "<start>" =>[["The", "<object>", "<verb>", "tonight."]],
"<object>"=>[["waves"], ["big", "yellow", "flowers"], ["slugs"]],
"<verb>" =>[["sigh", "<adverb>"], ["portend", "like", "<object>"],
["die", "<adverb>"]],
"<adverb>"=>[["warily"], ["grumpily"]]}
generate(hash, '<start>') #=> "The big yellow flowers die grumpily tonight."
generate(hash, '<start>') #=> "The waves die warily tonight."
generate(hash, '<start>') #=> "The slugs sigh warily tonight."
generate(hash, '<verb>') #=> "portend like big yellow flowers"
Explanation
Firstly, mod_hash
is constructed.
mod_hash = hash.transform_values{ |v| v.map { |a| a.join(' ') } }
#=> {"<start>" =>["The <object> <verb> tonight."],
# "<object>"=>["waves", "big yellow flowers", "slugs"],
# "<verb>" =>["sigh <adverb>", "portend like <object>", "die <adverb>"],
# "<adverb>"=>["warily", "grumpily"]}
Then the initial sentence is obtained.
start_key = '<start>'
sentence = mod_hash[start_key].sample
#=> "The <object> <verb> tonight."
We now simply replace each word in sentence
that begins '<'
and ends '>'
with a randomly-selected element of the value of that key in mod_hash
(the value being an array of strings). This continues until there are no more such words in sentence
.
The question mark in the regex means that one or more characters are to be matched lazily. That means that the match is terminated as soon as the first '>'
is encountered. If, for example, the sentence were "a <hat> and <cat>!"
, the regex would match both <hat>
and <cat>
. By contrast, if the match were greedy (the default), it would match "<hat> and <cat>"
, which of course is not a key of mod_hash
.
Note that hash
could have a structure that results in a non-terminating sequence of replacements.
See Hash#transform_values.