Резюме: Используйте два компактных индекса с двоичным поиском, одно из слов и одно из перевернутых слов. Стоимость места составляет 2N указателей для индексов; почти все поиски идут очень быстро; в худшем случае, «?? e», все еще приличный. Если вы создадите отдельные таблицы для каждой длины слова, это очень быстро сделает даже худший случай.
Подробности: Стивен С. опубликовал хорошая идея : поиск в упорядоченном словаре, чтобы найти диапазон, в котором может появиться шаблон. Это не помогает, однако, когда шаблон начинается с подстановочного знака. Вы также можете индексировать по длине слова, но вот еще одна идея: добавить упорядоченный индекс по обращенным словарным словам; тогда шаблон всегда дает небольшой диапазон либо в прямом индексе, либо в индексе обратного слова (поскольку нам говорят, что нет таких шаблонов, как «ABCD»). Сами слова должны быть сохранены только один раз, причем записи обеих структур указывают на одни и те же слова, а процедура поиска просматривает их либо вперед, либо назад; но чтобы использовать встроенную в Python функцию бинарного поиска, я вместо этого создал два отдельных массива строк, потратив некоторое пространство. (Я использую отсортированный массив вместо дерева, как предлагали другие, так как он экономит место и работает по крайней мере так же быстро.)
Код
import bisect, re
def forward(string): return string
def reverse(string): return string[::-1]
index_forward = sorted(line.rstrip('\n')
for line in open('/usr/share/dict/words'))
index_reverse = sorted(map(reverse, index_forward))
def lookup(pattern):
"Return a list of the dictionary words that match pattern."
if reverse(pattern).find('?') <= pattern.find('?'):
key, index, fixup = pattern, index_forward, forward
else:
key, index, fixup = reverse(pattern), index_reverse, reverse
assert all(c.isalpha() or c == '?' for c in pattern)
lo = bisect.bisect_left(index, key.replace('?', 'A'))
hi = bisect.bisect_right(index, key.replace('?', 'z'))
r = re.compile(pattern.replace('?', '.') + '$')
return filter(r.match, (fixup(index[i]) for i in range(lo, hi)))
Тесты: (Код также работает для таких шаблонов, как «AB? D», но без гарантии скорости.)
>>> lookup('hello')
['hello']
>>> lookup('??llo')
['callo', 'cello', 'hello', 'uhllo', 'Rollo', 'hollo', 'nullo']
>>> lookup('hel??')
['helio', 'helix', 'hello', 'helly', 'heloe', 'helve']
>>> lookup('he?l')
['heal', 'heel', 'hell', 'heml', 'herl']
>>> lookup('hx?l')
[]
Эффективность: Для этого требуется 2N указателей плюс пространство, необходимое для хранения текста словарного слова (в настроенной версии). Наихудшее время наступает на паттерне '?? e', который просматривает 44062 кандидата в моих 235 тыс. Слов / usr / share / dict / words; но почти все запросы выполняются намного быстрее, например, «h ?? lo», смотрящий на 190, и индексирование сначала по длине слова уменьшит «e» почти до нуля, если потребуется Каждая проверка кандидатов проходит быстрее, чем хэш-таблицы, которые предлагали другие.
Это напоминает решение rotations-index , которое позволяет избежать всех кандидатов на ложное совпадение за счет необходимости указывать около 10N указателей вместо 2N (предполагая, что средняя длина слова составляет около 10, как в my / usr /share/dict/words).
Вы можете выполнить один двоичный поиск для каждого поиска вместо двух, используя пользовательскую функцию поиска, которая выполняет поиск как по нижней, так и по верхней границе вместе (чтобы общая часть поиска не повторялась).