Как разрешить упражнение "Crypt Kicker", предложенное в "Программировании задач (Учебное пособие по программированию)"?
"Проблемы программирования (Учебное пособие по программированию)", вероятно, является одной из лучших книг по алгоритмам.
Я разрешил первые 11 упражнений, но теперь я застреваю с проблемой "Crypt Kicker":
Crypt Kicker
Общим, но небезопасным методом шифрования текста является перестановка букв алфавита. Другими словами, каждая буква алфавита последовательно заменяется текстом другой буквой. Чтобы гарантировать, что шифрование обратимо, никакие две буквы не заменяются одной буквой.
Ваша задача - расшифровать несколько закодированных строк текста, предполагая, что каждая строка использует другой набор замещений и что все слова в расшифрованном тексте являются словарями известных слов.
Input
Вход состоит из строки, содержащей целое число n, за которым следуют n строчных слов, по одному в строке, в алфавитном порядке. Эти n слов составляют словарь слов, который может отображаться в расшифрованном тексте.
После словаря есть несколько строк ввода. Каждая строка зашифровывается, как описано выше.
Словарь содержит не более 1000 слов. Количество слов не превышает 16 буквы. Зашифрованные строки содержат только строчные буквы и пробелы и не превышают 80 символов.
Вывод
Расшифруйте каждую строку и распечатайте ее до стандартного вывода. Если есть несколько решений, любой будет делать.
Если нет решения, замените каждую букву алфавита на звездочку.
Пример ввода 6
и
член
джейн
слоеное
пятно
Yertle
bjvg xsb hxsn xsb qymm xsb rqat xsb pnetfn
xxxx yyy zzzz www yyyy aaa bbbb ccc dddddd
Результат выборки
dick и jane, слоеный и пятнистый и yertle...
Какую стратегию я должен принять, чтобы разрешить это упражнение? Я думал о классическом и жестоком обратном пути, но я стараюсь избегать этого, пока не найду что-то более умное.
PS: Это не домашнее задание, я просто пытаюсь улучшить свои общие навыки.
Ответы
Ответ 1
KeyArray будет хранить таблицу замены.
-
Начните с пустого KeyArray, это версия 0
-
Сопоставление самого длинного зашифрованного слова с самым длинным словарем слова и добавление в KeyArray
(если есть два самых длинных, выберите любой), это версия 1.
-
Расшифруйте несколько букв следующего длинного зашифрованного слова.
- Проверьте, соответствуют ли расшифрованные буквы букве в том же
позицию в любом словарном словаре той же длины.
- Если нет совпадений, вернитесь к версии 0 и попробуйте другое слово.
-
Если некоторые буквы совпадают, добавьте остальные буквы в KeyArray, это версия 2.
-
Расшифруйте несколько букв следующего длинного зашифрованного слова.
- Проверьте, соответствуют ли расшифрованные буквы букве в том же
позицию в любом словарном словаре.
- Если нет совпадений, вернитесь к версии 1 и попробуйте другое слово
- Если некоторые буквы совпадают, добавьте остальные буквы в KeyArray, это версия 3.
Повторяйте, пока все слова не будут дешифрованы.
Если в версии 0 ни один из самых длинных слов не создает частичный дешифр в
более короткие слова, очень вероятно, что решения нет.
Ответ 2
Небольшая оптимизация может быть выполнена путем перечисления возможностей перед запуском backtracking. В 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))
Вывод:
{'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'])}
Если у вас есть одноэлементные возможности, вы можете устранить их из ввода и повторить алгоритм.
РЕДАКТИРОВАТЬ:. Переключится вместо списка и добавленного кода печати.
Ответ 3
Я попробовал совсем другой подход. Я построил три слова из словаря. Затем я рекурсивно прохожу через трю и предложение (пересекая три в DFS).
В каждом пространстве я убеждаюсь, что попал в конец слова в trie, и если так, я вернусь к корню. По пути я отслеживаю письма, которые я сделал до сих пор. Если когда-либо у меня есть задание, которое противоречит предыдущему назначению, я терплю неудачу и разгадываю рекурсию до такой степени, что могу сделать следующий возможный настрой.
Звучит сложно, но, похоже, это работает неплохо. И это действительно не так сложно кодировать!
Ответ 4
Другая возможная оптимизация, если у вас есть "достаточный" текст для работы, и вы знаете текстовый язык, вы можете использовать частоту писем (см. http://en.wikipedia.org/wiki/Letter_frequency
). Это, конечно, очень аппроксимативный подход при работе с 6/7 словами, но будет самым быстрым способом, если у вас есть несколько страниц для декодирования.
EDIT: о решении Max, вы можете попытаться извлечь некоторые характеристики этого слова, например, повторить буквы. Очевидно, что замечание, что слот в словаре и qymm в зашифрованном тексте являются единственными четырьмя буквами, заканчивающимися двойной буквой, дает прямой ответ на три буквы. В более сложных сценариях вы должны иметь возможность сузить возможности каждой пары букв.