Cách tạo trie trong Python


125

Tôi quan tâm đến các try và DAWG (biểu đồ từ xoay chiều trực tiếp) và tôi đã đọc rất nhiều về chúng nhưng tôi không hiểu tệp trie hoặc DAWG đầu ra trông như thế nào.

  • Một trie có nên là một đối tượng của các từ điển lồng nhau? Nơi mỗi chữ cái được chia thành các chữ cái và như vậy?
  • Việc tra cứu được thực hiện trên một từ điển như vậy có nhanh không nếu có 100k hoặc 500k mục nhập?
  • Cách triển khai các khối từ bao gồm nhiều hơn một từ được phân tách bằng - hoặc dấu cách?
  • Làm thế nào để liên kết tiền tố hoặc hậu tố của một từ với một phần khác trong cấu trúc? (cho DAWG)

Tôi muốn hiểu cấu trúc đầu ra tốt nhất để tìm ra cách tạo và sử dụng .

Tôi cũng sẽ đánh giá cao những gì nên là đầu ra của DAWG cùng với trie .

Tôi không muốn xem các biểu diễn đồ họa với các bong bóng được liên kết với nhau, tôi muốn biết đối tượng đầu ra sau khi một tập hợp các từ được chuyển thành các lần thử hoặc DAWG.


5
Đọc kmike.ru/python-data-structures để biết một cuộc khảo sát về cấu trúc dữ liệu kỳ lạ trong Python
Colonel Panic

Câu trả lời:


161

Về cơ bản, Unwind đúng là có nhiều cách khác nhau để triển khai một bộ ba; và đối với một bộ ba lớn, có thể mở rộng, các từ điển lồng nhau có thể trở nên cồng kềnh - hoặc ít nhất là không hiệu quả về dung lượng. Nhưng vì bạn chỉ mới bắt đầu, tôi nghĩ đó là cách tiếp cận dễ dàng nhất; bạn có thể viết mã đơn giản triechỉ trong vài dòng. Đầu tiên, một hàm để tạo 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_'}}}}

Nếu bạn không quen setdefault, nó chỉ cần tra một khóa trong từ điển (tại đây letterhoặc _end). Nếu khóa có mặt, nó trả về giá trị được liên kết; nếu không, nó sẽ gán giá trị mặc định cho khóa đó và trả về giá trị ( {}hoặc _end). (Nó giống như một phiên bản của getnó cũng cập nhật từ điển.)

Tiếp theo, một hàm để kiểm tra xem từ có trong bộ ba hay không:

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

Tôi sẽ để bạn chèn và loại bỏ như một bài tập.

Tất nhiên, gợi ý của Unwind sẽ không khó hơn nhiều. Có thể có một chút bất lợi về tốc độ là việc tìm kiếm nút phụ chính xác sẽ yêu cầu tìm kiếm tuyến tính. Nhưng tìm kiếm sẽ bị giới hạn ở số lượng ký tự có thể có - 27 nếu chúng tôi bao gồm _end. Ngoài ra, không có gì để đạt được bằng cách tạo ra một danh sách lớn các nút và truy cập chúng theo chỉ mục như anh ấy gợi ý; bạn cũng có thể chỉ cần lồng các danh sách.

Cuối cùng, tôi sẽ nói thêm rằng việc tạo biểu đồ từ xoay chiều có hướng (DAWG) sẽ phức tạp hơn một chút, bởi vì bạn phải phát hiện các tình huống trong đó từ hiện tại của bạn chia sẻ hậu tố với một từ khác trong cấu trúc. Trên thực tế, điều này có thể trở nên khá phức tạp, tùy thuộc vào cách bạn muốn cấu trúc DAWG! Bạn có thể phải tìm hiểu một số thông tin về khoảng cách Levenshtein để làm đúng.


1
Ở đó, thay đổi được thực hiện. Tôi muốn gắn bó với dict.setdefault()(nó chưa được sử dụng đầy đủ và gần như không đủ nổi tiếng), một phần vì nó giúp ngăn chặn các lỗi quá dễ tạo với một defaultdict(nơi bạn không nhận được KeyErrorđối với các khóa không tồn tại khi lập chỉ mục). Điều duy nhất bây giờ có thể làm cho nó có thể sử dụng được cho mã sản xuất là sử dụng _end = object():-)
Martijn Pieters

@MartijnPieters hmmm, tôi đặc biệt chọn không sử dụng đối tượng, nhưng tôi không thể nhớ tại sao. Có lẽ vì nó sẽ khó giải thích khi nhìn thấy trong bản demo? Tôi đoán tôi có thể tạo một đối tượng kết thúc với một đại diện tùy chỉnh
gửi

27

Có một cái nhìn tại đây:

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

Cấu trúc Trie tiết kiệm bộ nhớ tĩnh cho Python (2.x và 3.x).

Dữ liệu chuỗi trong MARISA-trie có thể chiếm bộ nhớ ít hơn tới 50x-100x so với trong một lệnh Python tiêu chuẩn; tốc độ tra cứu thô có thể so sánh được; trie cũng cung cấp các phương pháp nâng cao nhanh chóng như tìm kiếm tiền tố.

Dựa trên thư viện C ++ marisa-trie.

Đây là một bài đăng trên blog từ một công ty sử dụng thành công marisa trie:
https://www.repustate.com/blog/sharing-large-data-osystem-across-processes-python/

Tại Repustate, nhiều mô hình dữ liệu mà chúng tôi sử dụng trong phân tích văn bản của chúng tôi có thể được biểu diễn dưới dạng các cặp khóa-giá trị đơn giản hoặc từ điển bằng ngôn ngữ Python. Trong trường hợp cụ thể của chúng tôi, các từ điển của chúng tôi rất lớn, vài trăm MB mỗi từ điển và chúng cần được truy cập liên tục. Trên thực tế, đối với một yêu cầu HTTP nhất định, 4 hoặc 5 mô hình có thể được truy cập, mỗi mô hình thực hiện 20-30 tra cứu. Vì vậy, vấn đề mà chúng tôi phải đối mặt là làm thế nào để chúng tôi giữ cho mọi thứ nhanh chóng cho máy khách cũng như nhẹ nhất có thể cho máy chủ.

...

Tôi đã tìm thấy gói này, marisa cố gắng, đó là một trình bao bọc Python xung quanh việc triển khai C ++ của một trie marisa. “Marisa” là từ viết tắt của Thuật toán so khớp với StorAge được triển khai đệ quy. Điều tuyệt vời mà marisa cố gắng là cơ chế lưu trữ thực sự thu nhỏ dung lượng bộ nhớ bạn cần. Tác giả của plugin Python đã tuyên bố giảm kích thước 50-100X - trải nghiệm của chúng tôi cũng tương tự.

Điều tuyệt vời về gói trie marisa là cấu trúc trie bên dưới có thể được ghi vào đĩa và sau đó đọc vào thông qua một đối tượng được ánh xạ bộ nhớ. Với bộ nhớ marisa được ánh xạ, tất cả các yêu cầu của chúng tôi hiện đã được đáp ứng. Việc sử dụng bộ nhớ máy chủ của chúng tôi đã giảm đáng kể, khoảng 40% và hiệu suất của chúng tôi không thay đổi so với khi chúng tôi sử dụng triển khai từ điển của Python.

Ngoài ra còn có một số triển khai thuần python, mặc dù trừ khi bạn đang ở trên một nền tảng bị hạn chế, bạn muốn sử dụng triển khai được hỗ trợ C ++ ở trên để có hiệu suất tốt nhất:


cam kết cuối cùng là vào tháng 4 năm 2018, cam kết chính gần đây nhất là vào năm 2017
Boris

25

Đây là danh sách các gói python triển khai Trie:

  • marisa-trie - một triển khai dựa trên C ++.
  • python-trie - một cách triển khai python thuần túy đơn giản.
  • PyTrie - một triển khai python thuần túy tiên tiến hơn.
  • pygtrie - một triển khai python thuần túy của Google.
  • datrie - một triển khai ba mảng kép dựa trên libdatrie .

18

Được sửa đổi từ senderlephương thức của (ở trên). Tôi thấy rằng Python defaultdictlà lý tưởng để tạo trie hoặc cây tiền tố.

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

Hiểu biết của tôi về độ phức tạp của không gian là O (n * m). Một số có thảo luận ở đây. stackoverflow.com/questions/2718816/…
dapangmao

5
@dapangmao u đang sử dụng defaultdict chỉ cho char đầu tiên. Các ký tự còn lại vẫn sử dụng dict bình thường. Sẽ tốt hơn nếu sử dụng sắc lệnh mặc định lồng nhau.
lionelmessi

3
Trên thực tế, mã dường như không "sử dụng" defaultdict cho ký tự đầu tiên vì nó không đặt default_factory và vẫn đang sử dụng set_default.
studgeek

12

Không có "nên"; tuỳ bạn. Các triển khai khác nhau sẽ có các đặc điểm hiệu suất khác nhau, cần nhiều thời gian để triển khai, hiểu và làm đúng. Theo tôi, đây là điển hình cho việc phát triển phần mềm nói chung.

Trước tiên, tôi có thể sẽ thử tạo một danh sách chung của tất cả các nút trie cho đến nay và đại diện cho các con trỏ con trong mỗi nút dưới dạng danh sách các chỉ số trong danh sách chung. Với tôi, việc có một cuốn từ điển chỉ để trình bày liên kết con cảm thấy quá nặng nề.


2
một lần nữa, cảm ơn bạn, tuy nhiên tôi vẫn nghĩ rằng câu trả lời của bạn cần giải thích và làm rõ hơn một chút vì câu hỏi của tôi nhằm mục đích tìm ra logic và cấu trúc của chức năng của DAWG và TRIE. Đầu vào thêm của bạn sẽ rất hữu ích và được đánh giá cao.
Phil

Trừ khi bạn sử dụng các đối tượng có vị trí, không gian tên phiên bản của bạn sẽ là từ điển.
Mad Physicist

4

Nếu bạn muốn một TRIE được triển khai dưới dạng một lớp Python, đây là điều tôi đã viết sau khi đọc về chúng:

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
Cảm ơn bạn @NoctisSkytower. Điều này thật tuyệt khi bắt đầu nhưng tôi đã từ bỏ Python và TRIES hoặc DAWG do mức tiêu thụ bộ nhớ cực cao của Python trong các trường hợp này.
Phil

3
Đó là những gì ____slots____ dành cho. Nó làm giảm dung lượng bộ nhớ được sử dụng bởi một lớp, khi bạn có nhiều phiên bản của nó.
dstromberg

3

Phiên bản này đang sử dụng đệ quy

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)

Đầu ra:

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

Xác định Trie:

_trie = lambda: defaultdict(_trie)

Tạo Trie:

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

Tra cứu:

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

Kiểm tra:

print(word_exist(trie, 'cam'))

1
cảnh báo: lợi nhuận này Truechỉ cho toàn bộ văn bản, nhưng không phải cho tiền tố, tiền tố cho sự thay đổi return '_end' in currđếnreturn 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()

Ngoài

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

0

Lớp học Python cho Trie


Cấu trúc dữ liệu Trie có thể được sử dụng để lưu trữ dữ liệu trong O(L)đó L là độ dài của chuỗi, do đó, để chèn N chuỗi, độ phức tạp thời gian sẽ là O(NL)chuỗi có thể được tìm kiếm trongO(L) chỉ cùng một trường hợp xóa.

Có thể sao chép từ 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)

Code Oputpt

Đúng
Sai
['minakshi', 'minhaj']
7
minakshi
minhajsir
pari
parikshit
shubh
shubham
shubhi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.