
"""
Solve phone number -> word problem.

See http://www.norvig.com/java-lisp.html for a description of the problem.
"""

# Log:
# 4:10 start 4:20 finished reading problem 4:28 done coding 4:34 done debugging 4:39 done documenting
# too slow, optimized.  4:59 done optimizing, 5:16 fixed mis-reading of problem, 5:19 moved some
# comments around

import string

trans_table = string.maketrans('ejnqrwxdsyftamcivbkulopghz', '01112223334455666777888999')

def word_to_phone(s):
  """
  Translate a word in the dictionary to the corresponding string phone number (digits only)
  """
  return s.lower().translate(trans_table, '-"')

def phone_encodings(phone, phone_to_words, prev_is_number=False):
  """
  Yield all word encodings (as a list of individual word strings) of the given phone number.

  Here phone is the string phone number with non-digit chars stripped, phone_to_words
  is a dict where the keys represent partial phone numbers and the values are a list of all
  words in the dictionary which map exactly to the given partial phone number.  Finally,
  prev_is_number is a bool for whether the previous word was a number.
  """
  if len(phone) == 0:
    yield []
    return
  # Try all words to see which match the beginning of our current phone number.
  count = 0
  for sublen in range(1, len(phone)+1):
    num = phone[:sublen]
    for word in phone_to_words.get(num, []):
      count += 1
      # Recurse on the remaining part of the phone number.
      for rest in phone_encodings(phone[len(num):], phone_to_words):
        yield [word] + rest
  # Try digits iff no word matches and the previous word was not a digit.
  if not prev_is_number and count == 0:
    for rest in phone_encodings(phone[1:], phone_to_words, True):
      yield [phone[0]] + rest

def main(dictfile='dict.txt', phonefile='phone.txt'):
  """
  Solve phone encoding problem for given dictionary file (list of words) and phone file.
  """
  d = open(dictfile, 'rU').read().split()
  phones = open(phonefile, 'rU').read().split()

  # Build multimap from phone number parts to corresponding words that map exactly to that
  # number (see phone_encodings() docstring).
  phone_to_words = {}
  for word in d:
    num = word_to_phone(word)
    phone_to_words.setdefault(num, [])
    phone_to_words[num].append(word)
  for phone in phones:
    for encoding in phone_encodings(phone.replace('-', '').replace('/', ''), phone_to_words):
      print phone + ': ' + ' '.join(encoding)

if __name__ == '__main__':
  main()
