Как создать TRIE в Python
Я новичок в Python и пытаюсь учиться и совершенствоваться. Меня интересуют попытки и DAWGs, и я много читал об этом, но я не понимаю, как должен выглядеть выходной файл TRIE или DAWG.
- должен ли TRIE быть объектом вложенных словарей? Где каждая буква делится на буквы и так далее?
- будет ли поиск, выполняемый в таком словаре, быстрым, если есть записи 100k или 500k?
- как реализовать word-блоки состоит из нескольких слов, разделенных пробелом ?
- как связать префикс или суффикс слова с другой частью структуры? [для DAWG]
Я хочу понять лучшее структура производства для того, чтобы выяснить, как создать и использовать один.
Я также был бы признателен, что должно быть выход DAWG вместе с бор.
Я не хочу видеть графическое представление пузырьки связаны друг с другом, я видел их много во время чтения.
Я хотел бы знать выходной объект, как только набор слов превращается в попытки или DAWGs.
спасибо.
8 ответов
отдыха по существу правильно, что существует много разных способов реализации trie; и для большого масштабируемого trie вложенные словари могут стать громоздкими или, по крайней мере, неэффективными. Но поскольку вы только начинаете, я думаю, что это самый простой подход; вы можете закодировать простой trie
всего несколько строк. Во-первых, функция для построения дерева:
>>> _end = '_end_'
>>>
>>> def make_trie(*words):
... root = dict()
... for word in words:
... current_dict = root
... for letter in word:
... current_dict = current_dict.setdefault(letter, {})
... current_dict[_end] = _end
... return root
...
>>> make_trie('foo', 'bar', 'baz', 'barz')
{'b': {'a': {'r': {'_end_': '_end_', 'z': {'_end_': '_end_'}},
'z': {'_end_': '_end_'}}},
'f': {'o': {'o': {'_end_': '_end_'}}}}
если вы не знакомы с setdefault
, он просто ищет ключ в словарь (здесь letter
или _end
). Если ключ присутствует, он возвращает связанное значение; если нет, он назначает значение по умолчанию этому ключу и возвращает значение ({}
или _end
). (Это похоже на версию get
это также обновляет словарь.)
далее, функция для проверки того, находится ли слово в trie. Это может быть более кратким, но я оставляю его многословным, чтобы логика была ясна:
>>> def in_trie(trie, word):
... current_dict = trie
... for letter in word:
... if letter in current_dict:
... current_dict = current_dict[letter]
... else:
... return False
... else:
... if _end in current_dict:
... return True
... else:
... return False
...
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'baz')
True
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'barz')
True
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'barzz')
False
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'bart')
False
>>> in_trie(make_trie('foo', 'bar', 'baz', 'barz'), 'ba')
False
я выйду ввод и удаление к вам как упражнение.
конечно, предложение размотать было бы не намного сложнее. Может быть небольшой недостаток скорости в том, что поиск правильного подузла потребует линейного поиска. Но поиск будет ограничен количеством возможных символов - 27, Если мы включим _end
. Кроме того, нет ничего, что можно получить, создав массивный список узлов и получив к ним доступ по индексу, как он предлагает; вы можете просто вложить списки.
наконец, я добавлю, что создание DAWG было бы немного сложнее, потому что вам нужно обнаружить ситуации, в которых ваше текущее слово разделяет суффикс с другим словом в структуре. На самом деле, это может стать довольно сложным, в зависимости от того, как вы хотите структурировать DAWG! Возможно, вам придется узнать кое-что о Левенштейна расстояние чтобы сделать это правильно.
взгляните на это:
https://github.com/kmike/marisa-trie
статическая память-эффективные структуры Trie для Python (2.x и 3.икс.)
строковые данные в MARISA-trie могут занимать до 50x-100x меньше памяти, чем в стандартном Python dict; скорость сырого поиска сопоставима; trie также предоставляет быстрые передовые методы, такие как prefix search.
на основе marisa-trie C++ библиотека.
вот сообщение в блоге от компании, использующей marisa trie successfully:
http://blog.repustate.com/sharing-large-data-structure-across-processes-python/
в Repustate большая часть наших моделей данных, которые мы используем в нашем анализе текста, может быть представлена как простые пары ключ-значение или словари на языке Python. В нашем конкретном случае наши словари массивны, по несколько сотен МБ каждый, и к ним нужно постоянно обращаться. Фактически для данного HTTP-запроса могут быть доступны 4 или 5 моделей, каждая из которых выполняет 20-30 поисков. Так проблема в том, как мы храним вещи быстро для клиента, а также как можно легче для сервера.
...
Я нашел этот пакет, marisa пытается, который является оболочкой Python вокруг реализации C++ marisa trie. "Marisa" - это аббревиатура для алгоритма сопоставления с рекурсивно реализованным хранилищем. Что хорошего Мариса пытается хранения механизм действительно сжимает, сколько памяти вам нужно. Автор плагина Python утверждал, что 50-100X уменьшение размера-наш опыт аналогичен.
что замечательно в пакете marisa trie, так это то, что базовая структура trie может быть записана на диск, а затем прочитана с помощью объекта, сопоставленного с памятью. С памяти Мариса трие всем нашим требованиям отвечает. Использование памяти нашего сервера резко снизилось, примерно на 40%, и наша производительность не изменилась с тех пор, как мы реализация словарь в Python.
есть также несколько реализаций pure-python, хотя, если вы не находитесь на ограниченной платформе, вы хотели бы использовать реализацию с поддержкой C++ выше для лучшей производительности:
вот список пакетов python, которые реализуют Trie:
- Мариса-трие - реализация на основе C++.
- python-trie - простая реализация Pure python.
- PyTrie - более продвинутая реализация pure python.
нет никакого "должен"; это зависит от вас. Различные реализации будут иметь разные характеристики производительности, потребуется разное количество времени, чтобы реализовать, понять и получить право. На мой взгляд, это характерно для разработки программного обеспечения в целом.
Я бы, вероятно, сначала попытался создать глобальный список всех узлов trie до сих пор и представить дочерние указатели в каждом узле как список индексов в глобальный список. Имея словарь просто представлять ребенка связывание кажется мне слишком тяжелым.
изменена с senderle
's метод (выше). Я нашел, что Python defaultdict
идеально подходит для создания дерева trie или префикса.
from collections import defaultdict
class Trie:
"""
Implement a trie with insert, search, and startsWith methods.
"""
def __init__(self):
self.root = defaultdict()
# @param {string} word
# @return {void}
# Inserts a word into the trie.
def insert(self, word):
current = self.root
for letter in word:
current = current.setdefault(letter, {})
current.setdefault("_end")
# @param {string} word
# @return {boolean}
# Returns if the word is in the trie.
def search(self, word):
current = self.root
for letter in word:
if letter not in current:
return False
current = current[letter]
if "_end" in current:
return True
return False
# @param {string} prefix
# @return {boolean}
# Returns if there is any word in the trie
# that starts with the given prefix.
def startsWith(self, prefix):
current = self.root
for letter in prefix:
if letter not in current:
return False
current = current[letter]
return True
# Now test the class
test = Trie()
test.insert('helloworld')
test.insert('ilikeapple')
test.insert('helloz')
print test.search('hello')
print test.startsWith('hello')
print test.search('ilikeapple')
Если вы хотите, чтобы TRIE был реализован как класс Python, вот что я написал после прочтения о них:
class Trie:
def __init__(self):
self.__final = False
self.__nodes = {}
def __repr__(self):
return 'Trie<len={}, final={}>'.format(len(self), self.__final)
def __getstate__(self):
return self.__final, self.__nodes
def __setstate__(self, state):
self.__final, self.__nodes = state
def __len__(self):
return len(self.__nodes)
def __bool__(self):
return self.__final
def __contains__(self, array):
try:
return self[array]
except KeyError:
return False
def __iter__(self):
yield self
for node in self.__nodes.values():
yield from node
def __getitem__(self, array):
return self.__get(array, False)
def create(self, array):
self.__get(array, True).__final = True
def read(self):
yield from self.__read([])
def update(self, array):
self[array].__final = True
def delete(self, array):
self[array].__final = False
def prune(self):
for key, value in tuple(self.__nodes.items()):
if not value.prune():
del self.__nodes[key]
if not len(self):
self.delete([])
return self
def __get(self, array, create):
if array:
head, *tail = array
if create and head not in self.__nodes:
self.__nodes[head] = Trie()
return self.__nodes[head].__get(tail, create)
return self
def __read(self, name):
if self.__final:
yield name
for key, value in self.__nodes.items():
yield from value.__read(name + [key])
эта версия использует рекурсию
import pprint
from collections import deque
pp = pprint.PrettyPrinter(indent=4)
inp = raw_input("Enter a sentence to show as trie\n")
words = inp.split(" ")
trie = {}
def trie_recursion(trie_ds, word):
try:
letter = word.popleft()
out = trie_recursion(trie_ds.get(letter, {}), word)
except IndexError:
# End of the word
return {}
# Dont update if letter already present
if not trie_ds.has_key(letter):
trie_ds[letter] = out
return trie_ds
for word in words:
# Go through each word
trie = trie_recursion(trie, deque(word))
pprint.pprint(trie)
выход:
Coool <algos> python trie.py
Enter a sentence to show as trie
foo bar baz fun
{
'b': {
'a': {
'r': {},
'z': {}
}
},
'f': {
'o': {
'o': {}
},
'u': {
'n': {}
}
}
}
from collections import defaultdict
Определить Trie:
_trie = lambda: defaultdict(_trie)
Создать Trie:
trie = _trie()
for s in ["cat", "bat", "rat", "cam"]:
curr = trie
for c in s:
curr = curr[c]
curr.setdefault("_end")
Поиск:
def word_exist(trie, word):
curr = trie
for w in word:
if w not in curr:
return False
curr = curr[w]
return '_end' in curr