前回の記事で、実際のプロジェクトのニーズに応じて、現在の文字列クエリ マッチング アルゴリズムのより優れたものを調査したことを述べました。
「pyahocorasick - AC オートマトンに基づいた Python の効率的な文字列マッチングの実践」
この記事の主な目的は前の記事と同じです。ここでは主に、module-marisa-trie に一致する別の便利な文字列クエリを紹介します。公式プロジェクトのアドレスは、以下に示すように、 ここです。
現在の星の数は 1k 近くで、以前の AC オートマトン アルゴリズムよりわずかに多いことがわかります。
marisa-trie モジュールは、文字列キーの効率的な保存と取得のためのデータ構造を提供する Python ライブラリです。これは Trie (辞書ツリー) データ構造に基づいており、高度に圧縮された表現を使用して高速検索と低メモリ消費を実現します。
marisa-trie モジュールの詳細な機能は次のとおりです。
-
効率的な文字列取得: marisa-trie は、文字列を効果的に保存および取得できる Trie データ構造を使用します。効率的なプレフィックス検索、あいまい検索、範囲検索操作をサポートします。
-
圧縮ストレージ: このモジュールは、必要なストレージ容量を大幅に削減できる高度に圧縮された表現を使用します。これは、多数の文字列キーを保存する必要があるシナリオで特に役立ち、メモリとディスク領域を節約します。
-
高速検索: Trie データ構造を使用しているため、marisa-trie は一定時間に近い複雑さで検索操作を実行できます。これにより、高いパフォーマンスが必要な文字列キー検索タスクに最適になります。
-
ソート可能なキーのサポート: marisa-trie を使用すると、ソート可能なキーを保存および取得できます。これは、文字列キーの並べ替えや範囲クエリを必要とするアプリケーションで役立ちます。
-
使いやすい: このモジュールは、使いやすく、Python プロジェクトに統合しやすい、シンプルで直感的な API を提供します。
marisa-trie アルゴリズムは、Trie (辞書ツリー) データ構造に基づいて開発および構築され、高度に圧縮された表現を使用します。以下は、marisa-trie のアルゴリズム構築原理の詳細な説明です。
-
トライ データ構造: トライは、文字列のコレクションを保存および取得するために使用されるツリー データ構造です。ルート ノードから始まり、各ノードは文字を表し、各パスは文字列を表します。文字列の検索は、ツリー内のパスに沿って移動することによって実行されます。
-
トライの構築: marisa-trie を構築するには、まず文字列キーをトライに挿入する必要があります。文字列キーごとに、ルート ノードから開始してトライを文字順に下方向にトラバースし、必要に応じて新しいノードを作成します。文字列の終了文字に遭遇した場合、または一致を続行できない場合、そのノードは終了ノードとしてマークされます。
-
ノードの並べ替え: トライを構築した後、ノードを並べ替える必要があります。並べ替えの目的は、後続の圧縮ステップの準備をすることです。marisa-trie は、特定のソート アルゴリズムを使用して、トライ内の文字列キーの順序を維持します。
-
圧縮表現: ノードのソートが完了すると、marisa-trie は高度に圧縮された表現を使用します。同じプレフィックスを持つノードを共有プレフィックス ノードにマージします。これにより、特に多数の共有プレフィックスを持つ文字列キーの場合、ストレージ容量の要件が大幅に削減されます。
-
ビルドの完了: 圧縮表現が完了すると、marisa-trie のビルドが完了します。このように構築された marisa-trie データ構造は、ほぼ一定の時間計算量で効率的な文字列キー検索を実行できます。
marisa-trie のアルゴリズム構築ブロックにより、marisa-trie は、高いパフォーマンスと低メモリ消費を必要とする文字列キーの保存および取得タスクに適した効率的なデータ構造になります。
簡単な紹介はここまでです。次に、モジュールのアプリケーション パフォーマンスを練習して分析してみましょう。上記と同じデータを使用した、コア コードの実装は次のようになります。
patterns = ['an', 'un', 'is']
trie = marisa_trie.Trie(patterns)
text = 'In quiet solitude, peace is found,Where thoughts can wander, unbound.'
results = []
for i in range(len(text)):
matches = trie.prefixes(text[i:])
for matche in matches:
results.append((matche,i,i+len(matche)))
print(results)
結果の出力は次のようになります。
[('is', 25, 27), ('un', 30, 32), ('an', 50, 52), ('an', 54, 56), ('un', 61, 63), ('un', 65, 67)]
ここでの各サブオブジェクト ターゲットは、データの 3 つの要素です。最初の要素は、見つかったターゲット オブジェクト文字列です。次の 2 つの数字は、開始インデックスと、元の文字列テキスト内で現在一致する文字列の合計を表します。
次に、このモジュールのクエリ マッチング効率をその場でテストしたいので、以下に示すようにランダムな文字列を生成するテスト コードを記述します。
base_list=['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
print(len(base_list))
all_list=[1,10,100,1000,10000,100000]
t_list=[]
for one_all in all_list:
string=""
for i in range(one_all):
string+="".join(random.sample(base_list,10))
print("string_length: ", len(string))
以下に示すように、クエリ ターゲットはランダムに構築され、1000 回クエリされます。
for i in range(1000):
one_str="".join(random.sample(base_list, one_num))
one_patterns.append(one_str)
次のように marisa_trie 一致クエリを作成します。
trie = marisa_trie.Trie(one_patterns)
results = []
for i in range(len(string)):
matches = trie.prefixes(string[i:])
for matche in matches:
results.append((matche,i,i+len(matche)))
次に、実験を行います。実験では、クエリ部分文字列の長さと時間の関係も調査して分析します。ここでは、次のように個別に実験を行います。
[クエリの部分文字列の長さは 1]
[クエリの部分文字列の長さは 2]
[クエリの部分文字列の長さは 3]
[クエリの部分文字列の長さは 4]
[クエリの部分文字列の長さは 5]
[クエリの部分文字列の長さは 6]
[クエリの部分文字列の長さは 7]
[クエリの部分文字列の長さは 8]
[クエリの部分文字列の長さは 9]
[クエリの部分文字列の長さは 10 です]
実験テストの結果から判断すると、marisa_trie のクエリ パフォーマンスとターゲット クエリ部分文字列の長さとの間に明らかな関係はありません。同じクエリ条件下で、1 から 10 までの異なる長さの部分文字列の連続 10 グループのクエリ パフォーマンスをテストしました。全体的な傾向は同じであることがわかりました。
直感的な比較分析のために、ここでは以下に示すように全体的な比較視覚化曲線を描きます。
興味のある方はぜひ試してみてください、その威力を実感していただけると思います。