#!/usr/bin/python # # Jumble example: # This is an example of using strings, lists and dictionaries # in implementing a simple puzzle solver. # # Jumble is a game found in the San Francisco Chronicle. # The player is given letters from a word in random order # and the goal is to find the original word. # # There are at least a couple different ways of solving # this problem. For example, we can compute all permutations # of the given letters and check whether each one is a # real word. Another is to start with a list of real # words and check which ones have the same letter composition # (fingerprint) as the given set of letters. # # We are implementing the latter solution. In our program # words are Python strings. Fingerprints are Python dictionaries # whose keys are letters and whose values are the number # of occurrences of those letters (in short, a histogram). # The list of real words is kept in a Python dictionary # whose keys are the lengths of words and whose values are # Python lists of words of those lengths. (The choice to # use word length as the key is for easy elimination of # words that cannot possibly match the given input.) # # An interesting question is "which approach will be faster?" # Consider five-letter words (the statistics are similar for # six-letter words). There are about 3,000 to 4,000 "real" # words, depending on your dictionary. Is it faster to compute # the 120 (5*4*3*2*1) permutations of letters and check them # against a Python dictionary; or is it faster to compute # the fingerprints for all the "real" words and compare them # against the fingerprint of the given letters? # # Python 2/3 compatibility from __future__ import print_function try: read_input = raw_input except NameError: read_input = input # # Main program # def main(): """Main program""" # Load a dictionary of words then prompt users # for letters that they want to "unjumble" load('words') # or get a larger distionary at http://thinkpython.com/code/words.txt while True: line = read_input('Letters: ') characters = line.strip() if not characters: break # blank line means quit program print(jumble(characters)) def jumble(w): """Find a word in the dictionary with the same letters as 'w'""" matches = [] n = length(w) fp = fingerprint(w) for w in wordsOfLength(n): if fingerprint(w) == fp: # note that this compares two dictionaries! matches.append(w) return matches # # Functions for handling a dictionary of words # words = {} # key is number of characters, value is list of words with that many chars def wordsOfLength(l): """Return a list wof words of requested length 'l'""" return words.get(l, []) def load(filename): """Load dictionary of words from a file""" # Only keep words of length 5 and 6 because the # SF Chronicle jumble only uses words of those lengths with open(filename) as f: for line in f: w = line.strip() n = length(w) # Only keep words of length 5 and 6 because the # SF Chronicle jumble only uses words of those lengths if n != 5 and n != 6: continue try: words[n].append(w) # append another word to the list except KeyError: words[n] = [w] # first time through, create a list with just that word # print('words: ', words) # # Functions for handling words # def length(word): """Return number of alphabetic characters in 'word'""" count = 0 for c in word: if c.isalpha(): count = count + 1 return count def fingerprint(word): """Return fingerprint (character histogram) of 'word'""" fp = {} for c in word: if not c.isalpha(): continue c = c.lower() try: fp[c] = fp[c] + 1 except KeyError: fp[c] = 1 return fp # # If invoked directly (i.e., not via "import"), run the program # # Some words to try: lebjmu, siruv, soome, zugea, retine, timer, silly, python # if __name__ == '__main__': main()