Forum: Ruby Rhyming Words (#195)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-03-06 17:29
(Received via mailing list)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz:

1.  Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have elapsed from the time this message was
sent.

2.  Support Ruby Quiz by submitting ideas and responses
as often as you can! Visit: <http://rubyquiz.strd6.com>

3.  Enjoy!

Suggestion:  A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion.  Please reply to
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## Rhyming Words (#195)

Salutations fellow Rubyists,

This weeks quiz comes courtesy of Redd Vinylene:

  > How do I match words that rhyme, like end rhymes, last syllable
  > rhymes, double rhymes, beginning rhymes and first syllable rhymes?
  >
  > Like rhymer.com. I'm looking to improve my freestyle skills :)
  >
  > http://www.youtube.com/watch?v=SmqXKbxDoJ0

Your task is to create a Ruby program that when given a word and a
type of rhyme (end rhymes, last syllable rhymes, double rhymes,
beginning rhymes, first syllable rhymes) returns a list of rhyming
words.

Have Fun!
7cf3421cfce14b10556eefffc84ba232?d=identicon&s=25 Eugene Kalenkovich (Guest)
on 2009-03-11 05:05
(Received via mailing list)
"Daniel Moore" <yahivin@gmail.com> wrote in message
news:89672ce30903060825v33c1264eva295783120c794b4@mail.gmail.com...
>  > Like rhymer.com. I'm looking to improve my freestyle skills :)
>  >
>  > http://www.youtube.com/watch?v=SmqXKbxDoJ0
>
> Your task is to create a Ruby program that when given a word and a
> type of rhyme (end rhymes, last syllable rhymes, double rhymes,
> beginning rhymes, first syllable rhymes) returns a list of rhyming
> words.
>
> Have Fun!
>-----------------------------------------------------------------------------------

My solution takes the quiz idea, but not really its requirements :)

I've decided to drop the syllable-based rhymes only at first, as they
asked
for a pretty cumbersome pieces of code. After implementing and trying it
out
I've found that to my own taste _all_ types of rhymes described at
rhymer.com do not add enough to behavior to grant complications of code,
so
I've cleaned them out too :)

My final solution is limited to perfect and identical rhymes only.
First two commented lines - commands to run before to make it work.
You can see this working at  http://gamewords.herokugarden.com/


# wget http://www.dcs.shef.ac.uk/research/ilash/Moby/mpron.tar.Z
# tar zxvf mpron.tar.Z

$words = Hash.new{|h,k| h[k]=[]}
$rhymes = Hash.new{|h,k| h[k]=[]}

def perfect_key(pron)
  key = []
  pron.reverse.each do |snd|
    key << snd
    break if snd =~ /1$/
  end
  key
end

def rhymes(word)
  wup = word.upcase
  $rhymes[perfect_key($words[wup])] - [wup]
end

File.open('mpron/cmudict0.3') {|f| f.readlines}.each do |l|
  w, *pron = l.strip.split(' ')
  next unless !w.empty? and w.grep(/[^A-Z]/).empty?
  pron.map!{|sound| sound.sub(/2/,'1')}
  $words[w]=pron
  $rhymes[perfect_key(pron)] << w
end

input = ARGV.empty? ? ['laughter', 'soaring', 'antelope'] : ARGV
print input.map{|w| w+': '+rhymes(w).map(&:downcase).join(',
')+"\n"}.join("\n")
F745d7fbe0dbe947aa1082aabb2b6a4f?d=identicon&s=25 Redd Vinylene (reddvinylene)
on 2009-03-12 11:40
(Received via mailing list)
On Wed, Mar 11, 2009 at 5:03 AM, Eugene Kalenkovich
<rubify@softover.com>wrote:

> >  > rhymes, double rhymes, beginning rhymes and first syllable rhymes?
> > Have Fun!
> so
> $words = Hash.new{|h,k| h[k]=[]}
>
>  $rhymes[perfect_key(pron)] << w
> end
>
> input = ARGV.empty? ? ['laughter', 'soaring', 'antelope'] : ARGV
> print input.map{|w| w+': '+rhymes(w).map(&:downcase).join(',
> ')+"\n"}.join("\n")
>
>
Super duper!
33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-03-15 20:13
(Received via mailing list)
This week's quiz had an excellent solution from Eugene Kalenkovich.

In order to get this to work on my machine in 1.8 or 1.9 I had to add
the following:

    # Adding 1.9 and 1.8 compatibility
    if :a.respond_to? :to_proc
      class String
        include Enumerable
        alias :each :each_line
      end
    else
      class Symbol
        def to_proc
          proc { |obj, *args| obj.send(self, *args) }
        end
      end
    end

This is probably not the best overall solution to make code 1.9
compatible, but it does illustrate some of the differences between
Ruby versions. It appears as though the code was written for 1.8 but
with Facets or another library defining `Symbol#to_proc`.

Eugene's solution requires a database (plain text file of words and
pronunciations) to operate. The file contains entries in the following
form:

    # ...
    MONGOLS  M AA1 NG G AH0 L Z
    MONGOOSE  M AA1 NG G UW0 S
    # ...
    REPARATIONS  R EH2 P ER0 EY1 SH AH0 N Z
    REPARTEE  R EH2 P ER0 T IY1
    # ...

These are phonetic representations of the words. The readme file that
accompanies the database describes the pronunciations in detail if you
are interested in learning more.

    # lookup tables to store the words and rhyming words
    $words = Hash.new{|h,k| h[k]=[]}
    $rhymes = Hash.new{|h,k| h[k]=[]}

The `perfect_key` method is what determines what words perfectly rhyme
with one another. It iterates through the reverse of the sound list
and stops after adding the first stress. So if the word is 'mongoose'
`perfect_key` will return `["S", "UW0", "G", "NG", "AA1"]`.

    # Returns an array of sounds starting at the end and
    # going through the first stress
    def perfect_key(pron)
      key = []
      pron.reverse.each do |snd|
        key << snd
        break if snd =~ /1$/
      end
      key
    end

The rhymes method is used to display the rhyming words in the output.
It gets all the rhyming words for the given word's perfect key and
returns the list minus the word itself.

    def rhymes(word)
      wup = word.upcase
      $rhymes[perfect_key($words[wup])] - [wup]
    end

Reading the data file and creating the tables of pronunciation and
words is the bulk of the processing. This section iterates through
each line in the word data file and creates a mapping from the word to
the pronunciation, skipping comment lines or lines that have words
with non-alpha characters. After mapping the word to the pronunciation
it adds the word to the list of rhymes that is mapped by the perfect
key of the pronunciation in the `$rhymes` hash.

    File.open('mpron/cmudict0.3') {|f| f.readlines}.each do |l|
      w, *pron = l.strip.split(' ')
      next unless !w.empty? and w.grep(/[^A-Z]/).empty?
      pron.map!{|sound| sound.sub(/2/,'1')}
      $words[w] = pron
      $rhymes[perfect_key(pron)] << w
    end

The program will find rhymes for all command line arguments passed in,
or some example arguments if none are given. Having already loaded the
data into a mapping that is convenient, all that is left is to lookup
the lists of words that match the given words perfect rhymes and print
them out.

    input = ARGV.empty? ? ['laughter', 'soaring', 'antelope'] : ARGV
    results = input.map do |w|
      w + ': ' + rhymes(w).map(&:downcase).join(', ') + "\n"
    end.join("\n")

    print results

Thank you, Eugene, for a very cool solution!

http://rubyquiz.strd6.com/quizzes/195

- Daniel
Cec345a59245af9d06e4438a413f4eb5?d=identicon&s=25 Shot (Piotr Szotkowski) (Guest)
on 2009-04-17 21:06
(Received via mailing list)
Daniel Moore:

> This is probably not the best overall solution to make code 1.9
> compatible, but it does illustrate some of the differences between
> Ruby versions. It appears as though the code was written for 1.8 but
> with Facets or another library defining `Symbol#to_proc`.

shot@devielle:~$ ruby -v; irb --simple-prompt
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
>> Symbol.instance_methods.include? 'to_proc'
=> true
>> ['DoWN', 'CaSe'].map &:downcase
=> ["down", "case"]

But yes, it definitely *does* illustrate
the differences between Ruby versions. :)

— Shot
This topic is locked and can not be replied to.