कैसे पायथन में एक तिकड़ी बनाने के लिए


125

मैं कोशिश कर रहा हूँ और DAWGs (प्रत्यक्ष चक्रीय शब्द ग्राफ) में दिलचस्पी है और मैं उनके बारे में बहुत कुछ पढ़ रहा हूं, लेकिन मुझे समझ नहीं आ रहा है कि आउटपुट trie या DAWG फ़ाइल कैसी दिखनी चाहिए।

  • क्या एक तिकड़ी नेस्टेड शब्दकोशों की एक वस्तु होनी चाहिए? प्रत्येक अक्षर को अक्षरों में और कहाँ विभाजित किया जाता है?
  • यदि 100k या 500k प्रविष्टियाँ हैं, तो क्या ऐसे शब्दकोश पर एक प्रदर्शन तेज़ होगा?
  • शब्द-ब्लॉक को लागू करने के लिए कैसे एक -या एक से अधिक शब्द अंतरिक्ष या के साथ अलग है?
  • संरचना में किसी शब्द के उपसर्ग या प्रत्यय को कैसे जोड़ा जाए? (DAWG के लिए)

मैं सबसे अच्छी आउटपुट संरचना को समझना चाहता हूं ताकि यह पता लगाया जा सके कि किसी को कैसे बनाया जाए और उसका उपयोग कैसे किया जाए।

मैं भी जानना चाहेंगे कि क्या किया जाना चाहिए एक DAWG के उत्पादन के साथ-साथ trie

मैं एक दूसरे से जुड़े बुलबुले के साथ चित्रमय अभ्यावेदन नहीं देखना चाहता, मैं एक बार शब्दों का एक सेट कोशिश या DAWGs में बदल जाने के बाद आउटपुट ऑब्जेक्ट जानना चाहता हूं।


5
पाइथन में विदेशी डेटा संरचनाओं के सर्वेक्षण के लिए kmike.ru/python-data-structures पढ़ें
कर्नल पैनिक

जवाबों:


161

अनवाइंड अनिवार्य रूप से सही है कि ट्राई को लागू करने के कई अलग-अलग तरीके हैं; और एक बड़े, स्केलेबल ट्राई के लिए, नेस्टेड डिक्शनरी बोझिल हो सकती हैं - या कम से कम जगह अक्षम हो सकती है। लेकिन जब से आप बस शुरू कर रहे हैं, मुझे लगता है कि यह सबसे आसान तरीका है; आप 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उस संस्करण की तरह है जो शब्दकोश को भी अपडेट करता है।)

अगला, यह परीक्षण करने के लिए कि क्या शब्द त्रि में है:

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

मैं एक अभ्यास के रूप में आपके लिए प्रविष्टि और निष्कासन छोड़ दूंगा।

बेशक, अनविंड का सुझाव ज्यादा कठिन नहीं होगा। इसमें थोड़ी गति का नुकसान हो सकता है जिसमें सही उप-नोड को खोजने के लिए एक रैखिक खोज की आवश्यकता होगी। लेकिन खोज संभव पात्रों की संख्या तक सीमित होगी - यदि हम शामिल करते हैं तो 27 _end। इसके अलावा, नोड्स की एक विशाल सूची बनाने और इंडेक्स द्वारा उन्हें एक्सेस करने से कुछ भी हासिल होने वाला नहीं है, जैसा कि वह सुझाव देता है; आप सूची को केवल घोंसला बना सकते हैं।

अंत में, मैं यह जोड़ूंगा कि एक निर्देशित एसाइक्लिक शब्द ग्राफ (DAWG) बनाना थोड़ा अधिक जटिल होगा, क्योंकि आपको उन स्थितियों का पता लगाना होगा, जिसमें आपका वर्तमान शब्द संरचना में दूसरे शब्द के साथ एक प्रत्यय साझा करता है। वास्तव में, यह बल्कि जटिल हो सकता है, इस पर निर्भर करता है कि आप DAWG की संरचना कैसे करना चाहते हैं! आपको इसे ठीक करने के लिए लेवेंसहाइट दूरी के बारे में कुछ चीजें सीखनी पड़ सकती हैं ।


1
वहां, बदलाव किया गया। मैं इसके साथ रहना चाहता हूँ dict.setdefault()(यह बहुत अच्छी तरह से जाना जाता है और लगभग अच्छी तरह से ज्ञात नहीं है), क्योंकि यह उन बग्स को रोकने में मदद करता है जो एक के साथ बनाना बहुत आसान है defaultdict(जहां आपको KeyErrorअनुक्रमण पर गैर-मौजूदा कुंजियों के लिए नहीं मिलेगा )। केवल एक चीज है कि अब यह उत्पादन कोड के लिए useable होगा उपयोग कर रहा है _end = object():-)
मार्टिन पीटर्स

@MartijnPieters हम्म, मैंने विशेष रूप से ऑब्जेक्ट का उपयोग नहीं करने के लिए चुना है, लेकिन मुझे याद नहीं है कि क्यों। शायद इसलिए कि डेमो में देखने पर इसकी व्याख्या करना कठिन होगा? मुझे लगता है कि मैं एक कस्टम repr
प्रेषक

27

कृपया एक नज़र इसे देखिये:

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

पायथन (2.x और 3.x) के लिए स्थिर मेमोरी-कुशल ट्राई संरचनाएं।

एक MARISA-trie में स्ट्रिंग डेटा मानक पायथन की तुलना में 50x-100x कम मेमोरी तक ले सकता है; कच्चे देखने की गति तुलनीय है; तीनों भी उपसर्ग खोज की तरह तेजी से उन्नत तरीके प्रदान करता है।

मारिसा-ट्राइ सी ++ लाइब्रेरी के आधार पर।

यहाँ marisa trie का सफलतापूर्वक उपयोग करने वाली कंपनी का एक ब्लॉग पोस्ट है:
https://www.repustate.com/blog/sharing-large-data-structure-across-processes-python/

पुनरावर्ती में, हमारे पाठ विश्लेषण में हमारे द्वारा उपयोग किए जाने वाले बहुत से डेटा मॉडल को सरल कुंजी-मूल्य जोड़े, या पायथन लिंगो में शब्दकोशों के रूप में दर्शाया जा सकता है। हमारे विशेष मामले में, हमारे शब्दकोश बड़े पैमाने पर हैं, कुछ सौ एमबी प्रत्येक, और उन्हें लगातार एक्सेस करने की आवश्यकता है। वास्तव में दिए गए HTTP अनुरोध के लिए, 4 या 5 मॉडल एक्सेस किए जा सकते हैं, प्रत्येक 20-30 लुकअप कर रहे हैं। तो हम जो समस्या का सामना करते हैं वह यह है कि कैसे हम क्लाइंट के लिए और साथ ही साथ सर्वर के लिए संभव के रूप में प्रकाश को तेज रखें।

...

मुझे यह पैकेज मिला, मारिसा कोशिश करती है, जो कि सी + + के चारों ओर एक पायरी रैपर है, जो मरीसा ट्राइ का कार्यान्वयन करता है। "मारिसा" रिकर्सिएबल इम्प्लीमेंटेड स्टोरेज के साथ मैचिंग एलगोरिदम के लिए एक परिचित है। मेरीसा कोशिशों के बारे में बहुत अच्छी बात है कि भंडारण तंत्र वास्तव में सिकुड़ जाता है कि आपको कितनी स्मृति की आवश्यकता है। पायथन प्लगइन के लेखक ने आकार में 50-100X कमी का दावा किया - हमारा अनुभव समान है।

मारिसा ट्राइ पैकेज के बारे में बहुत अच्छी बात यह है कि अंतर्निहित ट्राई संरचना को डिस्क पर लिखा जा सकता है और फिर मेमोरी मैप्ड ऑब्जेक्ट के माध्यम से पढ़ा जा सकता है। एक मेमोरी मैप्ड मरीसा ट्राइ के साथ, हमारी सभी आवश्यकताएं अब पूरी हो गई हैं। हमारे सर्वर की मेमोरी का उपयोग लगभग 40% तक नाटकीय रूप से कम हो गया, और पायथन के शब्दकोश कार्यान्वयन का उपयोग करने पर हमारा प्रदर्शन अपरिवर्तित रहा।

शुद्ध-अजगर कार्यान्वयन के एक जोड़े भी हैं, हालांकि जब तक आप एक प्रतिबंधित मंच पर नहीं होते हैं तब तक आप सर्वश्रेष्ठ प्रदर्शन के लिए ऊपर दिए गए C ++ समर्थित कार्यान्वयन का उपयोग करना चाहते हैं:


आखिरी प्रतिबद्धता अप्रैल 2018 में थी, आखिरी बड़ी प्रतिबद्धता 2017 की तरह थी
बोरिस

25

यहाँ अजगर पैकेजों की एक सूची है जो ट्राइ को लागू करते हैं:

  • marisa-trie - एक C ++ आधारित कार्यान्वयन।
  • अजगर- trie - एक साधारण शुद्ध अजगर कार्यान्वयन।
  • PyTrie - एक अधिक उन्नत शुद्ध अजगर कार्यान्वयन।
  • pygtrie - Google द्वारा एक शुद्ध अजगर कार्यान्वयन।
  • datrie - एक डबल सरणी trie के आधार पर कार्यान्वयन libdatrie

18

संशोधित senderleविधि (ऊपर) से। मैंने पाया कि पायथन defaultdictट्रीज़ या प्रीफ़िक्स ट्री बनाने के लिए आदर्श है।

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

अंतरिक्ष जटिलता की मेरी समझ हे (n * m) है। कुछ की चर्चा यहाँ है। stackoverflow.com/questions/2718816/…
dapangmao

5
@dapangmao u डिफ़ॉल्ट का उपयोग केवल पहले चार के लिए कर रहे हैं। बाकी के चार्ट अभी भी सामान्य हुक्म का उपयोग करते हैं नेस्टेड डिफॉल्ट का उपयोग करना बेहतर होगा।
सिंहलमेसी

3
वास्तव में, कोड पहले वर्ण के लिए डिफ़ॉल्ट का उपयोग करके "या तो" नहीं लगता है क्योंकि यह default_factory सेट नहीं करता है और अभी भी set_default का उपयोग कर रहा है।
स्टूजेक

12

कोई "नहीं" होना चाहिए; यह आप पर निर्भर करता है। विभिन्न कार्यान्वयन में अलग-अलग प्रदर्शन विशेषताएं होंगी, लागू करने, समझने और सही होने के लिए विभिन्न मात्रा में समय लगेगा। यह मेरी राय में, एक पूरे के रूप में सॉफ्टवेयर विकास के लिए विशिष्ट है।

मैं शायद सबसे पहले सभी ट्राइ नोड्स की एक वैश्विक सूची बनाने की कोशिश करूंगा, और प्रत्येक नोड में बच्चे-पॉइंटर्स का प्रतिनिधित्व करते हुए वैश्विक सूची में सूचकांक की सूची के रूप में। बच्चे को जोड़ने का प्रतिनिधित्व करने के लिए सिर्फ एक शब्दकोश का होना मुझे बहुत भारी लगता है।


2
एक बार फिर, धन्यवाद, हालांकि मुझे अभी भी लगता है कि आपके उत्तर को थोड़ा और अधिक गहन स्पष्टीकरण और स्पष्टीकरण की आवश्यकता है क्योंकि मेरा प्रश्न DAWGs और TRIEs की कार्यक्षमता के तर्क और संरचना का पता लगाना है। आपका आगे का इनपुट बहुत उपयोगी और सराहनीय होगा।
फिल

जब तक आप स्लॉट के साथ वस्तुओं का उपयोग नहीं करते हैं, तब तक आपके उदाहरण के नाम स्थान वैसे भी होंगे।
मैड फिजिसिस्ट

4

यदि आप एक पायथन को पायथन वर्ग के रूप में लागू करना चाहते हैं, तो यहां उनके बारे में पढ़ने के बाद कुछ लिखा गया है:

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
साभार @NoctisSkytower यह शुरू करने के लिए बहुत अच्छा है, लेकिन मैं इन मामलों परिदृश्यों में पायथन की अत्यधिक उच्च स्मृति खपत के कारण अजगर और ट्राइसेज़ या डीएडब्ल्यूजी पर छोड़ दिया।
फिल

3
यही ____slots____ के लिए है। यह एक वर्ग द्वारा उपयोग की जाने वाली स्मृति की मात्रा को कम कर देता है, जब आपके पास इसके कई उदाहरण हैं।
dstromberg

3

यह संस्करण पुनरावृत्ति का उपयोग कर रहा है

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

3
from collections import defaultdict

तीनों को परिभाषित करें:

_trie = lambda: defaultdict(_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

परीक्षा:

print(word_exist(trie, 'cam'))

1
सावधानी: यह Trueकेवल एक पूरे शब्द के लिए लौटता है, लेकिन उपसर्ग के लिए नहीं, उपसर्ग परिवर्तन के return '_end' in currलिएreturn True
श्रीकांत शेट

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

बाहर

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

0

तीनों के लिए पायथन क्लास


डेटा को स्टोर करने के लिए Trie Data Structure का उपयोग किया जा सकता है O(L)जहां L स्ट्रिंग की लंबाई है इसलिए N स्ट्रिंग्स को समय पर सम्मिलित करने के लिए जटिलता होगी O(NL)स्ट्रिंग को O(L)केवल हटाने के लिए उसी में खोजा जा सकता है ।

Https://github.com/Parikshit22/pytrie.git से क्लोन किया जा सकता है

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)

कोड Oputpt

यह सच है
झूठी
[ 'मीनाक्षी', 'मिन्हाज']
7
मीनाक्षी
minhajsir
Pari
परीक्षित
शुभ
शुभम
shubhi

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.