web-dev-qa-db-ja.com

PythonでTRIEを作成する方法

私はPythonが初めてで、学び、前進しようとしています。私はTRIEとDAWGに興味があり、それについて多くのことを読んでいますが、出力TRIEまたはDAWGファイルがどのように見えるべきか理解できません。

  • TRIEはネストされた辞書のオブジェクトである必要がありますか?各文字が文字などに分割される場所はどこですか?
  • 100kまたは500kのエントリがある場合、そのような辞書で実行されるルックアップは高速になりますか?
  • -またはスペースで区切られた複数のWordで構成されるワードブロックを実装するにはどうすればよいですか?
  • Wordのプレフィックスまたはサフィックスを構造内の別の部分にリンクする方法は? [DAWGの場合]

最高の出力構造を理解して、それを作成して使用する方法を理解したいです。

また、TRIEとともにDAWGの出力であるべきことも感謝します。

互いにリンクされたバブルのグラフィカルな表示を見たくありません。読みながらそれらをたくさん見ました。

単語のセットがTRIEまたはDAWGに変換されたら、出力オブジェクトを知りたいです。

ありがとうございました。

110
Phil

nwind は、トライを実装する多くの異なる方法があることを本質的に正しいです。また、大規模でスケーラブルなトライでは、ネストされた辞書が扱いにくくなるか、少なくともスペースが非効率になる場合があります。しかし、あなたはまだ始まったばかりなので、私はそれが最も簡単なアプローチだと思います。簡単な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のバージョンのようなものです。)

次に、Wordがトライにあるかどうかをテストする関数。これはもっと簡潔かもしれませんが、論理を明確にするために冗長にしています。

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

挿入と取り外しは演習として行います。

もちろん、Unwindの提案はそれほど難しくありません。正しいサブノードを見つけるには線形検索が必要になるため、わずかな速度の欠点があります。ただし、検索は可能な文字数に制限されます。_endを含めると27です。また、彼が示唆するように、ノードの大規模なリストを作成し、インデックスによってそれらにアクセスすることによって得られるものは何もありません。リストをネストすることもできます。

最後に、現在のWordが構造内の別のWordと接尾辞を共有している状況を検出する必要があるため、DAWGの作成はもう少し複雑になると付け加えます。実際、DAWGの構成方法によっては、これはかなり複雑になる可能性があります。 Levenshtein距離 を正しく理解するには、いくつかのことを学ぶ必要があるかもしれません。

143
senderle

これを見てください:

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

Python(2.xおよび3.x)の静的メモリ効率の良いトライ構造。

MARISA-trieの文字列データは、標準のPython dictよりも最大50x-100x少ないメモリを使用できます。生のルックアップ速度は同等です。 trieは、プレフィックス検索などの高速で高度な方法も提供します。

Marisa-trie C++ライブラリに基づいています。

これは、Marisa Trieを正常に使用している会社のブログ投稿です。
https://www.repustate.com/blog/sharing-large-data-structure-across-processes-python/

Repustateでは、テキスト分析で使用するデータモデルの多くを、単純なキーと値のペア、またはPython lingoの辞書として表すことができます。私たちの特定のケースでは、私たちの辞書は巨大で、それぞれ数百MBあり、それらは常にアクセスする必要があります。実際、特定のHTTPリクエストに対して、それぞれ20〜30個のルックアップを行う4つまたは5つのモデルにアクセスできます。したがって、私たちが直面している問題は、クライアントのためにできるだけ速く、サーバーのためにできるだけ軽く保つ方法です。

...

Marisaがこのパッケージを見つけました。これは、marisaトライのC++実装のPythonラッパーです。 「Marisa」は、アルゴリズムと再帰的に実装されたStorAgeの頭字語です。マリサの試みの素晴らしい点は、ストレージメカニズムが本当に必要なメモリ量を減らすことです。 Pythonプラグインの作成者は、サイズが50〜100倍削減されたと主張しました。私たちの経験も同様です。

マリサトライパッケージの優れている点は、基礎となるトライ構造をディスクに書き込んでから、メモリマップされたオブジェクトを介して読み込むことができることです。メモリマップドマリサトライにより、すべての要件が満たされました。サーバーのメモリ使用量は約40%劇的に低下し、パフォーマンスはPythonの辞書実装を使用したときと変わりませんでした。

いくつかのpure-python実装もありますが、制限されたプラットフォームを使用している場合を除き、最高のパフォーマンスを得るために上記のC++を実装した実装を使用する必要があります。

26
Anentropic

Trieを実装するpythonパッケージのリストは次のとおりです。

17
Tzach

「すべき」というものはありません。それはあなた次第です。さまざまな実装は、異なるパフォーマンス特性を持ち、実装、理解、および正しい実行にさまざまな時間を要します。私の意見では、これはソフトウェア開発全体の典型です。

おそらく最初に、これまでに作成されたすべてのトライノードのグローバルリストを作成し、各ノードの子ポインターをグローバルリストへのインデックスのリストとして表現しようとします。子供のリンクを表すためだけに辞書を持っているのは、私にとっては重すぎると感じています。

13
unwind

senderleのメソッド(上記)から変更。 Pythonの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')
11
dapangmao

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])
4
Noctis Skytower

このバージョンは再帰を使用しています

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
naren
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
DingLi
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
Mous