Python'da bir trie nasıl oluşturulur


125

Denemeler ve DAWG'lerle (doğrudan döngüsel olmayan kelime grafiği) ilgileniyorum ve onlar hakkında çok şey okuyorum ama çıktı trie veya DAWG dosyasının nasıl görünmesi gerektiğini anlamıyorum.

  • Bir üçlü, iç içe geçmiş sözlüklerin bir nesnesi olmalı mı? Her harfin harflere bölündüğü yer vb.
  • Böyle bir sözlükte yapılan arama 100 bin veya 500 bin giriş varsa hızlı olur mu?
  • Birden fazla kelime -veya boşluktan oluşan kelime blokları nasıl uygulanır ?
  • Bir kelimenin ön ekini veya son ekini yapının başka bir bölümüne nasıl bağlayabilirim? (DAWG için)

Nasıl oluşturulacağını ve kullanılacağını anlamak için en iyi çıktı yapısını anlamak istiyorum .

Ben de ne olması gerektiğini takdir ediyorum bir dawg çıktısını birlikte trayden .

Birbirine bağlı baloncuklarla grafiksel gösterimler görmek istemiyorum, bir kelime kümesi denemelere veya DAWG'lere dönüştürüldüğünde çıktı nesnesini bilmek istiyorum.


5
Python'daki egzotik veri yapılarının araştırması için kmike.ru/python-data-structures sayfasını okuyun
Colonel Panic

Yanıtlar:


161

Çözme , bir trie gerçekleştirmenin birçok farklı yolu olduğu konusunda esasen doğrudur; ve büyük, ölçeklenebilir bir üçlü için, iç içe geçmiş sözlükler kullanışsız hale gelebilir - veya en azından alan verimsiz olabilir. Ama daha yeni başladığınız için, bence bu en kolay yaklaşım; triesadece birkaç satırda basit bir kod yazabilirsiniz. İlk olarak, trie'yi oluşturmak için bir fonksiyon:

>>> _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_'}}}}

Aşina değilseniz setdefault, sözlükte bir anahtar arar (burada letterveya _end). Anahtar varsa, ilişkili değeri döndürür; değilse, bu tuşa varsayılan bir değer atar ve değeri ( {}veya _end) döndürür . (Bunun bir versiyonu gibi getsözlüğü de günceller.)

Ardından, kelimenin trie içinde olup olmadığını test etmek için bir işlev:

>>> def in_trie(trie, word):
...     current_dict = trie
...     for letter in word:
...         if letter not in current_dict:
...             return False
...         current_dict = current_dict[letter]
...     return _end in current_dict
... 
>>> 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

Yerleştirme ve çıkarmayı bir alıştırma olarak size bırakacağım.

Elbette, Unwind'ın önerisi çok daha zor olmazdı. Doğru alt düğümü bulmanın doğrusal bir arama gerektirmesi bakımından hafif bir hız dezavantajı olabilir. Ancak arama, olası karakter sayısıyla sınırlı olacaktır - dahil edersek 27 _end. Ayrıca, devasa bir düğüm listesi oluşturarak ve önerdiği gibi indeksle erişerek kazanılacak hiçbir şey yoktur; Listeleri iç içe geçirseniz iyi olur.

Son olarak, yönlendirilmiş çevrimsiz kelime grafiği (DAWG) oluşturmanın biraz daha karmaşık olacağını ekleyeceğim, çünkü mevcut kelimenizin yapıdaki başka bir kelimeyle bir son ek paylaştığı durumları tespit etmeniz gerekiyor. Aslında, DAWG'yi nasıl yapılandırmak istediğinize bağlı olarak bu oldukça karmaşık bir hal alabilir! Doğru yapmak için Levenshtein mesafesi hakkında bazı şeyler öğrenmeniz gerekebilir .


1
Orada değişiklik yapıldı. Ben ile sopa dict.setdefault(), kısmen çok kolay bir ile oluşturmak için vardır böcek önlemeye yardımcı olur, çünkü (o atıl ve neredeyse yeterince tanınmış değil) defaultdict(eğer bir olsun istiyorum nereye KeyErrorendeksleme üzerinde var olmayan anahtarlar için). Artık onu üretim kodu için kullanılabilir kılacak tek şey _end = object():-)
Martijn Pieters

@MartijnPieters hmmm, özellikle nesneyi kullanmamayı seçtim ama nedenini hatırlayamıyorum. Belki de demoda görüldüğünde yorumlamak zor olacağı için? Sanırım özel bir repr ile bir son nesne yapabilirim
2019

27

Şuna bir bak:

https://github.com/kmike/marisa-trie

Python (2.x ve 3.x) için statik bellek açısından verimli Trie yapıları.

Bir MARISA-trie'deki dize verileri, standart bir Python diktesine göre 50x-100x daha az bellek alabilir; ham arama hızı karşılaştırılabilir; trie ayrıca önek araması gibi hızlı gelişmiş yöntemler sağlar.

Marisa-trie C ++ kitaplığına dayanmaktadır.

İşte marisa trie'yi başarıyla kullanan bir şirketten bir blog yazısı:
https://www.repustate.com/blog/sharing-large-data-structure-across-processes-python/

Repustate'te, metin analizimizde kullandığımız veri modellerimizin çoğu basit anahtar-değer çiftleri veya Python dilinde sözlükler olarak temsil edilebilir. Bizim özel durumumuzda, sözlüklerimiz çok büyük, her biri birkaç yüz MB ve sürekli erişilmeleri gerekiyor. Aslında belirli bir HTTP isteği için, her biri 20-30 arama yapan 4 veya 5 modele erişilebilir. Dolayısıyla, karşılaştığımız sorun, işleri istemci için nasıl hızlı tutacağımız ve sunucu için olabildiğince hafif tutacağımızdır.

...

Bu paketi buldum, marisa çalışır, bu bir marisa trie'nin C ++ uygulaması etrafında bir Python sarmalayıcısıdır. "Marisa" Yinelemeli Olarak Uygulanan StorAge ile Eşleştirme Algoritmasının kısaltmasıdır. Marisa denemelerinin harika yanı, depolama mekanizmasının ihtiyacınız olan belleğin miktarını gerçekten küçültmesidir. Python eklentisinin yazarı, boyutta 50-100 kat azalma olduğunu iddia etti - deneyimlerimiz benzer.

Marisa trie paketi ile ilgili harika olan şey, alttaki trie yapısının diske yazılabilmesi ve daha sonra bellek eşlemeli bir nesne aracılığıyla okunabilmesidir. Bellek haritalanmış bir marisa trie ile artık tüm gereksinimlerimiz karşılanıyor. Sunucumuzun bellek kullanımı, yaklaşık% 40 oranında önemli ölçüde azaldı ve performansımız Python'un sözlük uygulamasını kullandığımıza göre değişmedi.

Ayrıca birkaç saf python uygulaması da vardır, ancak kısıtlı bir platformda değilseniz, en iyi performans için yukarıdaki C ++ destekli uygulamayı kullanmak isteyebilirsiniz:


son taahhüt Nisan 2018'deydi, son büyük taahhüt 2017 gibiydi
Boris


18

Den Modified senderlebireyin yöntemi (yukarıda). Python'un defaultdictbir trie veya önek ağacı oluşturmak için ideal olduğunu buldum .

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')

Uzay karmaşıklığı anlayışım O (n * m). Bazılarının burada tartışması var. stackoverflow.com/questions/2718816/…
dapangmao

5
@dapangmao u sadece ilk karakter için defaultdict kullanıyor. Dinlenme karakterleri hala normal dikteyi kullanır. İç içe geçmiş defaultdict kullanmak daha iyi olur.
lionelmessi

3
Gerçekte, kod ya default_factory'yi ayarlamadığı ve hala set_default'u kullandığı için ilk karakter için defaultdict'i "kullanıyor" gibi görünmüyor.
studgeek

12

"Gerekir" yoktur; Sana kalmış. Çeşitli uygulamalar, farklı performans özelliklerine sahip olacak, uygulanması, anlaşılması ve düzeltilmesi çeşitli miktarlarda zaman alacaktır. Bence bu, bir bütün olarak yazılım geliştirme için tipik bir durum.

Muhtemelen ilk önce şimdiye kadar yaratılmış olan tüm trie düğümlerinin genel bir listesine sahip olmayı ve her düğümdeki çocuk işaretçileri global listedeki indekslerin bir listesi olarak temsil etmeyi deneyeceğim. Sadece bağlantı kuran çocuğu temsil etmek için bir sözlüğe sahip olmak bana çok ağır geliyor.


2
bir kez daha teşekkür ederim, ancak yine de cevabınızın biraz daha derin açıklamaya ve açıklamaya ihtiyacı olduğunu düşünüyorum, çünkü sorum DAWG'lerin ve TRIE'lerin işlevselliğinin mantığını ve yapısını anlamaya yönelik. Daha sonraki girdiniz çok faydalı ve takdir edilecektir.
Phil

Yuvalı nesneler kullanmadığınız sürece, örnek ad alanınız yine de sözlük olacaktır.
Mad Physicist

4

Bir TRIE'nin Python sınıfı olarak uygulanmasını istiyorsanız, işte onlar hakkında okuduktan sonra yazdığım bir şey:

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])

2
Teşekkürler @NoctisSkytower. Bu başlamak için harika ama ben bu senaryolarda Python'un aşırı yüksek bellek tüketimi nedeniyle Python ve TRIES veya DAWG'lerden vazgeçtim.
Phil

3
____Slots____ bunun içindir. Çok sayıda örneğiniz olduğunda, bir sınıf tarafından kullanılan bellek miktarını azaltır.
dstromberg

3

Bu sürüm özyineleme kullanıyor

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)

Çıktı:

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': {}
    }
  }
}

3
from collections import defaultdict

Trie'yi tanımlayın:

_trie = lambda: defaultdict(_trie)

Trie oluşturun:

trie = _trie()
for s in ["cat", "bat", "rat", "cam"]:
    curr = trie
    for c in s:
        curr = curr[c]
    curr.setdefault("_end")

Yukarı Bak:

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

Ölçek:

print(word_exist(trie, 'cam'))

1
Dikkat: Bu Truesadece tam bir kelime için döner , ancak ön ek için değil, ön ek değişikliği return '_end' in curriçinreturn True
Shrikant Shete

0
class Trie:
    head = {}

    def add(self,word):

        cur = self.head
        for ch in word:
            if ch not in cur:
                cur[ch] = {}
            cur = cur[ch]
        cur['*'] = True

    def search(self,word):
        cur = self.head
        for ch in word:
            if ch not in cur:
                return False
            cur = cur[ch]

        if '*' in cur:
            return True
        else:
            return False
    def printf(self):
        print (self.head)

dictionary = Trie()
dictionary.add("hi")
#dictionary.add("hello")
#dictionary.add("eye")
#dictionary.add("hey")


print(dictionary.search("hi"))
print(dictionary.search("hello"))
print(dictionary.search("hel"))
print(dictionary.search("he"))
dictionary.printf()

Dışarı

True
False
False
False
{'h': {'i': {'*': True}}}

0

Trie için Python Sınıfı


Trie Veri Yapısı, O(L)L'nin dizenin uzunluğu olduğu yerlerde verileri depolamak için kullanılabilir, böylece N dizge eklemek için zaman karmaşıklığı O(NL)dize O(L)yalnızca aynı silme işleminde aranabilir .

Https://github.com/Parikshit22/pytrie.git adresinden klonlanabilir

class Node:
    def __init__(self):
        self.children = [None]*26
        self.isend = False
        
class trie:
    def __init__(self,):
        self.__root = Node()
        
    def __len__(self,):
        return len(self.search_byprefix(''))
    
    def __str__(self):
        ll =  self.search_byprefix('')
        string = ''
        for i in ll:
            string+=i
            string+='\n'
        return string
        
    def chartoint(self,character):
        return ord(character)-ord('a')
    
    def remove(self,string):
        ptr = self.__root
        length = len(string)
        for idx in range(length):
            i = self.chartoint(string[idx])
            if ptr.children[i] is not None:
                ptr = ptr.children[i]
            else:
                raise ValueError("Keyword doesn't exist in trie")
        if ptr.isend is not True:
            raise ValueError("Keyword doesn't exist in trie")
        ptr.isend = False
        return
    
    def insert(self,string):
        ptr = self.__root
        length = len(string)
        for idx in range(length):
            i = self.chartoint(string[idx])
            if ptr.children[i] is not None:
                ptr = ptr.children[i]
            else:
                ptr.children[i] = Node()
                ptr = ptr.children[i]
        ptr.isend = True
        
    def search(self,string):
        ptr = self.__root
        length = len(string)
        for idx in range(length):
            i = self.chartoint(string[idx])
            if ptr.children[i] is not None:
                ptr = ptr.children[i]
            else:
                return False
        if ptr.isend is not True:
            return False
        return True
    
    def __getall(self,ptr,key,key_list):
        if ptr is None:
            key_list.append(key)
            return
        if ptr.isend==True:
            key_list.append(key)
        for i in range(26):
            if ptr.children[i]  is not None:
                self.__getall(ptr.children[i],key+chr(ord('a')+i),key_list)
        
    def search_byprefix(self,key):
        ptr = self.__root
        key_list = []
        length = len(key)
        for idx in range(length):
            i = self.chartoint(key[idx])
            if ptr.children[i] is not None:
                ptr = ptr.children[i]
            else:
                return None
        
        self.__getall(ptr,key,key_list)
        return key_list
        

t = trie()
t.insert("shubham")
t.insert("shubhi")
t.insert("minhaj")
t.insert("parikshit")
t.insert("pari")
t.insert("shubh")
t.insert("minakshi")
print(t.search("minhaj"))
print(t.search("shubhk"))
print(t.search_byprefix('m'))
print(len(t))
print(t.remove("minhaj"))
print(t)

Kod Oputpt

True
False
['minakshi', 'minhaj']
7
minakshi
minhajsir
pari
parikshit
shubh
shubham
shubhi

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.