CarTalk's Homophones Puzzler: A Programmatic Solution The puzzler:


  This was sent in by a fellow named Dan O'Leary. He came upon a common
  one-syllable, five-letter word recently that has the following unique
  property. When you remove the first letter, the remaining letters form
  a homophone of the original word, that is a word that sounds exactly
  the same. Replace the first letter, that is, put it back and remove
  the second letter and the result is yet another homophone of the
  original word. And the question is, what's the word?
  
  Now I'm going to give you an example that doesn't work. Let's look at
  the five-letter word, 'wrack.' W-R-A-C-K, you know like to 'wrack with
  pain.' If I remove the first letter, I am left with a four-letter
  word, 'R-A-C-K.' As in, 'Holy cow, did you see the rack on that buck!
  It must have been a nine-pointer!' It's a perfect homophone. If you
  put the 'w' back, and remove the 'r,' instead, you're left with the
  word, 'wack,' which is a real word, it's just not a homophone of the
  other two words.


I've used CMU Pronouncing Dictionary and modified it (manually) to keep alphabetical strings only.

My code: 

def read_dict(file_path):
    """Reads 'CMU Pronouncing Dictionary' text file.

       returns: dict mapping from a word to a string describing
       its pronounciation

       file_path: str
       """
    fin = open(file_path)
    pron_dict = {}

    for line in fin:
        index = line.find(' ')  # e.g. "AA  EY2 EY1"
        word = line[:index] 
        pron = line[index+2:]
        pron_dict[word] = pron

    return pron_dict

def are_homophones(pron_dict, word1, word2, word3):
    """Returns whether 3 words are homophones.

       pron_dict: dict
       """

    for word in [word1, word2, word3]:
        if not word in pron_dict:
            return False

    return pron_dict[word1] == pron_dict[word2] == pron_dict[word3]

def find_homophones_words(pron_dict):
    """Returns a list of words, where:

    * word
    * word with the first item omitted 
    * word with the second item omitted

    are all homophones and existing in pron_dict.

    pron_dict: dict
    """

    homophones = []    

    for word in pron_dict:
        first_word = word[1:]  # word with the first letter omitted
        second_word = word[0] + word[2:] # word with the second letter omitted      
        if are_homophones(pron_dict, word, first_word, second_word):
                homophones.append(word)   

    return homophones

pron_dict = read_dict('CMUDict.txt')
homophones = find_homophones_words(pron_dict)

for word in homophones:
    print([word, word[1:], word[0] + word[2:]])


Notes:


I haven't, strictly, followed the puzzler. The code finds words satisfying
the main requirements, except that the word is "one-syllable and five-letters".
I'm a hobbyist and a beginner.
I don't only want to optimize the code, but also my skills and style. Therefore any notes, advice and suggestions aren't only welcomed, but also encouraged!
There's an efficiency improvement you could make to are_homophones: As is, you check that all three words are in pron_dict before checking that they're the same. However, it's better practice to catch errors using try blocks rather than trying to prevent them from happening (see this Stack Overflow post). A cleaner/faster way way to write this would be 

def are_homophones(pron_dict, word1, word2, word3):
    try:
        return pron_dict[word1] == pron_dict[word2] == pron_dict[word3]
    except KeyError:
        return False


It's also good practice to use with blocks to open files so that Python automatically handles closing the file. With this change, read_dict becomes

def read_dict(file_path):
    pron_dict = {}

    with open(file_path) as fin:
        for line in fin:
            index = line.find(' ')  # e.g. "AA  EY2 EY1"
            word = line[:index] 
            pron = line[index+2:]
            pron_dict[word] = pron

    return pron_dict