Próbuję zaimplementować Patricię Trie metodami addWord()
, isWord()
i isPrefix()
jako sposobu na przechowywanie dużego słownika słów w celu szybkiego pobrania (w tym wyszukiwania prefiksów). Przeczytałem te koncepcje, ale one po prostu nie wyjaśniają implementacji. Chcę wiedzieć (w języku Java lub w języku Python) jak zaimplementować Trie, szczególnie węzły (lub czy powinienem je rekursywnie implementować). Widziałem jedną osobę, która zaimplementowała ją z tablicą 26 węzłów potomnych ustawioną na null/None. Czy istnieje lepsza strategia (na przykład traktowanie liter jako bitów) i jak ją wdrożyć?Implementacja Patricii Trie do użycia jako słownik
Odpowiedz
Ktoś jeszcze zadał pytanie na temat Patricii, która próbowała jakiś czas temu, a ja myślałem o wprowadzeniu w życie Pythona, ale tym razem postanowiłem dać mu szansę (tak, to jest droga za burtą, ale wydawało mi się to miłe mały projekt). To, co zrobiłem, może nie jest czystą implementacją Patricii, ale lubię swoją drogę. Inne Patricia próbuje (w innych językach) użyć tylko listy dla dzieci i sprawdzić każde dziecko, aby zobaczyć, że jest dopasowanie, ale myślałem, że to raczej nieefektywne, więc używam słowników. Oto w zasadzie, jak to skonfigurowałem:
Zacznę od głównego węzła. Katalog główny to tylko słownik. Słownik ma klucze, które są wszystkie pojedyncze znaki (pierwsze litery słów) prowadzące do oddziałów. Wartości odpowiadające każdemu kluczowi są listami, gdzie pierwszy element jest ciągiem, który daje resztę łańcucha, który pasuje do tego odgałęzienia trie, a drugi element jest słownikiem prowadzącym do dalszych gałęzi z tego węzła. Słownik ten ma również pojedyncze klawisze znaków, które odpowiadają pierwszej literze reszty słowa, a proces jest kontynuowany w dół.
Inną rzeczą, o której powinienem wspomnieć, jest to, że jeśli dany węzeł ma gałęzie, ale jest również słowem w samym tytule, oznacza to, że ma klucz ''
w słowniku, który prowadzi do węzła z listą ['',{}]
.
Oto mały przykład, który pokazuje, jak słowa są przechowywane (węzeł główny jest zmienna _d
):
>>> x = patricia()
>>> x.addWord('abcabc')
>>> x._d
{'a': ['bcabc', {}]}
>>> x.addWord('abcdef')
>>> x._d
{'a': ['bc', {'a': ['bc', {}], 'd': ['ef', {}]}]}
>>> x.addWord('abc')
{'a': ['bc', {'a': ['bc', {}], '': ['', {}], 'd': ['ef', {}]}]}
Zauważ, że w tym ostatnim przypadku, klucz „” została dodana do słownika oznaczać, że "abc" jest słowem dodanym do "abcdef" i "abcabc".
kod źródłowy
class patricia():
def __init__(self):
self._data = {}
def addWord(self, word):
data = self._data
i = 0
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
if data:
data[word[i:i+1]] = [word[i+1:],{}]
else:
if word[i:i+1] == '':
return
else:
if i != 0:
data[''] = ['',{}]
data[word[i:i+1]] = [word[i+1:],{}]
return
i += 1
if word.startswith(node[0],i):
if len(word[i:]) == len(node[0]):
if node[1]:
try:
node[1]['']
except KeyError:
data = node[1]
data[''] = ['',{}]
return
else:
i += len(node[0])
data = node[1]
else:
ii = i
j = 0
while ii != len(word) and j != len(node[0]) and \
word[ii:ii+1] == node[0][j:j+1]:
ii += 1
j += 1
tmpdata = {}
tmpdata[node[0][j:j+1]] = [node[0][j+1:],node[1]]
tmpdata[word[ii:ii+1]] = [word[ii+1:],{}]
data[word[i-1:i]] = [node[0][:j],tmpdata]
return
def isWord(self,word):
data = self._data
i = 0
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
return False
i += 1
if word.startswith(node[0],i):
if len(word[i:]) == len(node[0]):
if node[1]:
try:
node[1]['']
except KeyError:
return False
return True
else:
i += len(node[0])
data = node[1]
else:
return False
def isPrefix(self,word):
data = self._data
i = 0
wordlen = len(word)
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
return False
i += 1
if word.startswith(node[0][:wordlen-i],i):
if wordlen - i > len(node[0]):
i += len(node[0])
data = node[1]
else:
return True
else:
return False
def removeWord(self,word):
data = self._data
i = 0
while 1:
try:
node = data[word[i:i+1]]
except KeyError:
print "Word is not in trie."
return
i += 1
if word.startswith(node[0],i):
if len(word[i:]) == len(node[0]):
if node[1]:
try:
node[1]['']
node[1].pop('')
except KeyError:
print "Word is not in trie."
return
data.pop(word[i-1:i])
return
else:
i += len(node[0])
data = node[1]
else:
print "Word is not in trie."
return
__getitem__ = isWord
Można zauważyć, że w końcu mogę ustawić __getitem__
metody isWord. Oznacza to, że zostanie zwrócony tekst "abc" w kliencie lub nie.
Myślę, że może powinienem zrobić moduł z tego i przesłać go do PyPI, ale potrzebuje więcej testów i przynajmniej metody usuwania. Jeśli znajdziesz jakieś błędy, daj mi znać, ale wygląda na to, że działa całkiem nieźle. Ponadto, jeśli zauważysz jakąkolwiek znaczącą poprawę wydajności, chciałbym również o tym usłyszeć. Zastanawiałem się nad zrobieniem czegoś z opróżnianiem słowników na dole każdej gałęzi, ale na razie to zostawiam. Te puste słowniki można zastąpić danymi związanymi ze słowem, aby na przykład rozszerzyć zastosowania implementacji.
W każdym razie, jeśli nie podoba ci się sposób, w jaki go zaimplementowałem, przynajmniej może to da ci kilka pomysłów na temat tego, jak chcesz wprowadzić własną wersję.
@John Tak, ale oznaczałem je tak, aby kod był łatwiejszy do zrozumienia dla innych. Mogę wprowadzić takie zmiany w ostatecznej wersji. Dzięki za wejście. –
@Justin: Przepraszam, w swoim pośpiechu zostawiłem jedno: wlen -> l –
@John Tak, wszystkie są dobre do zmniejszenia rozmiaru pliku .py (co nie było moim celem w tym momencie), ale jak to naprawdę pomaga w wydajności? –
Oto rekurencyjna realizacja przy użyciu metod bardziej pythonic:
def matching_prefix_index(word1, word2):
max_len = min(len(word1),len(word2))
for i in range(max_len):
if word2[i] != word1[i]:
return i
return max_len
class PatriciaTrie(object):
def __init__(self):
self._storage = {}
self._complete_prefix_flag = False
def _find_storage_key(self, word):
for key in self._storage:
prefix_index = matching_prefix_index(key, word)
if prefix_index > 0:
return (key, prefix_index)
return (None, None)
def add(self, word):
if word == '':
self._complete_prefix_flag = True
return True
key, prefix_index = self._find_storage_key(word)
if key is not None:
if prefix_index == len(key):
return self._storage[key].add(word[len(key):])
else:
new_tree = PatriciaTrie()
new_tree._storage[key[prefix_index:]] = self._storage.pop(key)
self._storage[key[0:prefix_index]] = new_tree
return new_tree.add(word[prefix_index:])
else:
self._storage[word] = PatriciaTrie()
self._storage[word].add('')
return True
def remove(self, word):
if word == '':
self._complete_prefix_flag = False
return True
key, prefix_index = self._find_storage_key(word)
if key is None or prefix_index != len(key):
return False
subword = word[prefix_index:]
subtrie = self._storage[key]
if subtrie.remove(subword):
if (not subtrie._complete_prefix_flag) and len(subtrie._storage) == 0:
self._storage.pop(key)
return True
else:
return False
def __contains__(self, word):
if word == '':
return self._complete_prefix_flag
key, prefix_index = self._find_storage_key(word)
if key is None or prefix_index != len(key):
return False
else:
return (word[prefix_index:] in self._storage[key])
def has_prefix(self, word):
if word == '':
return True
key, prefix_index = self._find_storage_key(word)
if key is None:
return False
elif len(key) > len(word):
return (prefix_index == len(word))
elif len(key) != prefix_index:
return False
else:
return self._storage[key].has_prefix(word[prefix_index:])
- 1. Implementacja Trie
- 2. "Prosta" implementacja narzędzia Trie
- 3. Trie oszczędza miejsce, ale jak?
- 4. Trie realizacji w Guava?
- 5. Pismo maszynowe: Jak zadeklarować słownik do użycia z _.map?
- 6. Słownik z Func jako kluczem
- 7. słownik z delegatem jako wartość
- 8. Clojure: Jak wygenerować "trie"?
- 9. Nieskalarne ENV do użycia jako parametr Symfony
- 10. Implementacja Odroczony obiekt bez użycia jquery
- 11. Słownik dwukierunkowy?
- 12. Struktury danych Trie - Java
- 13. Załaduj duży słownik w języku Python zakodowany jako Json bez użycia pamięci?
- 14. Jak przekazać słownik jako argument wiersza poleceń do skryptu Pythona?
- 15. Trie (drzewo prefiksów) w Pythonie
- 16. Dlaczego można rozpakować słownik jako krotkę?
- 17. Słownik C# do .csv
- 18. Dołącz słownik do słownika?
- 19. Jak przekonwertować słownik do tablicy
- 20. Clojure Zipper zagnieżdżonych map represyjnych TRIE
- 21. Implementacja kolejkowania bez użycia bloków kończy się pętlą pod obciążeniem
- 22. Implementacja operatora modulo jako funkcji w C
- 23. JavaScript: jak serializować element DOM jako ciąg do późniejszego użycia?
- 24. Deklarowanie obiektu ArrayList jako ostatecznego do użycia w pliku stałych
- 25. Czy Apache Kafka jest odpowiedni do użycia jako kolejka zadań?
- 26. Podaj typ klasy jako parametr do użycia w ArrayList?
- 27. Zapisywanie katalogu jako zmiennej do późniejszego użycia w skrypcie linux
- 28. Objaśnienie kodu Ruby do budowania struktur danych TRIE
- 29. Gdzie znajdę standardową implementację map opartą na Trie w Javie?
- 30. Konwersja Python słownik do listy
Czy to zadanie domowe? –
Opiera się na zadaniu domowym, ale zakres tego pytania jest tylko ułamkiem tego zadania i jest (szczególnie teraz) bardziej odpowiedni do mojego osobistego zrozumienia tego, jak działają próby, niż do uzyskania oceny. –
Chciałem tylko powiedzieć, że chciałem zbadać Patricię i raz spróbowałem, i zakończyłem rezygnację. Zauważyłem, że ekspozycja Knutha jest zbyt lakoniczna, a pierwotna praca nie miała dla mnie żadnego sensu. –