How do I resolve the “Crypt Kicker” exercise proposed in “Programming Challenges (The Programming Contest Training Manual)”?

≡放荡痞女 提交于 2019-11-30 11:32:18
Carlos Gutiérrez

KeyArray will hold the replacement table.

  • Start with an empty KeyArray, this is version 0

  • Match longest encrypted word to longest dictionary word and add to KeyArray (if there are two longest, pick any), this is version 1.

  • Decrypt some letters of the next longest crypted word.

  • Check if the decrypted letters match the letter in the same position in any dictionary word of the same length.
  • If none matches, go back to version 0 and try another word.
  • If some letters match, add the rest of the letters to KeyArray, this is version 2.

  • Decrypt some letters of the next longest crypted word.

  • Check if the decrypted letters match the letter in the same position in any dictionary word.
  • If none matches, go back to version 1 and try another word
  • If some letters match, add the rest of the letters to KeyArray, this is version 3.

Repeat until all words are decrypted.

If at version 0 none of the longest words creates a partial decrypt in shorter words, very probably there is no solution.

A minor optimization could be done by enumerating possibilities before the backtracking run. In Python:

dictionary = ['and', 'dick', 'jane', 'puff', 'spot', 'yertle']
line = ['bjvg', 'xsb', 'hxsn', 'xsb', 'qymm', 'xsb', 'rqat', 'xsb', 'pnetfn']

# ------------------------------------

import collections

words_of_length = collections.defaultdict(list)

for word in dictionary:
  words_of_length[len(word)].append(word)

possibilities = collections.defaultdict(set)
certainities = {}

for word in line:
    length = len(word)
    for i, letter in enumerate(word):
        if len(words_of_length[length]) == 1:
            match = words_of_length[length][0]
            certainities[letter] = match[i]
        else:
            for match in words_of_length[length]:
              possibilities[letter].add(match[i])

for letter in certainities.itervalues():
    for k in possibilities:
        possibilities[k].discard(letter)

for i, j in certainities.iteritems():
    possibilities[i] = set([j])

# ------------------------------------

import pprint
pprint.pprint(dict(possibilities))

Output:

{'a': set(['c', 'f', 'o']),
 'b': set(['d']),
 'e': set(['r']),
 'f': set(['l']),
 'g': set(['f', 'k']),
 'h': set(['j', 'p', 's']),
 'j': set(['i', 'p', 'u']),
 'm': set(['c', 'f', 'k', 'o']),
 'n': set(['e']),
 'p': set(['y']),
 'q': set(['i', 'j', 'p', 's', 'u']),
 'r': set(['j', 'p', 's']),
 's': set(['n']),
 't': set(['t']),
 'v': set(['c', 'f', 'o']),
 'x': set(['a']),
 'y': set(['i', 'p', 'u'])}

If you have some single-element possibilities, you can eliminate them from the input and rerun the algorithm.

EDIT: Switched to set instead of list and added printing code.

I actually tried a rather different approach. I built a trie from the dictionary words. Then I walk through the trie and the sentence together recursively (traversing the trie in a DFS).

At each space I make sure I hit the end of a word in the trie and if so I loop back to the root. Along the way I keep track of the letter assignments I've made so far. If ever I have an assignment that contradicts a prior assignment I fail and unravel the recursion to the point I can make the next possible assigment.

It sounds tricky but it seems to work quite well. And it is really not that hard to code up!

Another possible optimization, if you have "enough" text to deal with and you know the text's language, you can use letter frequencies (see : http://en.wikipedia.org/wiki/Letter_frequency). This is of course a very approximative approach when dealing with 6 / 7 words but will be the fastest way if you have a few pages to decode.

EDIT : about Max's solution, you could try to extract some characteristics of the word, too, such as repeating letters. Obviously, remarking that puff in the dictionary and qymm in the encrypted text are the only four letter words ending with a double letter gives a straight answer for 3 of the letters. In more complex scenarios, you should be able to narrow the possibilities for each letter couple.

Here is a Java implementation with more refinements to the algorithm proposed by @Carlos Gutiérrez.

Crypt Kicker Algorithm and Solution, what went wrong?

  • The refinement is to add a word pattern to reduce the search space for words. For example, words "abc" and "her" have the same pattern while "aac" and "her" don't as a three distinct letters word would not match a tow letters distinct word.

  • Moreover, the algorithm can be implemented recursively which is more intuitive and sensible.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!