Leveinstein(leveinsteinまたはdifflib)のようなアルゴリズムを使用すると、おおよその一致を簡単に見つけることができます。
>>> import difflib
>>> difflib.SequenceMatcher(None,"amazing","amaging").ratio()
0.8571428571428571
あいまい一致は、必要に応じてしきい値を決定することで検出できます。
現在の要件:大きい文字列のしきい値に基づいてファジー部分文字列を検索する。
例えば。
large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"
#result = "manhatan","manhattin" and their indexes in large_string
強引な解決策の1つは、長さN-1からN + 1(または他の一致する長さ)のすべての部分文字列を生成することです。ここで、Nはquery_stringの長さであり、それらに1つずつレベンシュタインを使用して、しきい値を確認します。
pythonで利用できるより良い解決策はありますか、できればpython 2.7に含まれるモジュール、または外部で利用可能なモジュールです。
[〜#〜] update [〜#〜]:Python regexモジュールはかなりうまく機能しますが、組み込みのre
モジュールより少し遅いですがファジー部分文字列の場合、これは追加の操作による明らかな結果です。目的の出力は良好であり、あいまいさの大きさの制御は簡単に定義できます。
>>> import regex
>>> input = "Monalisa was painted by Leonrdo da Vinchi"
>>> regex.search(r'\b(leonardo){e<3}\s+(da)\s+(vinci){e<2}\b',input,flags=regex.IGNORECASE)
<regex.Match object; span=(23, 41), match=' Leonrdo da Vinchi', fuzzy_counts=(0, 2, 1)>
Reを置き換える予定の新しい正規表現ライブラリには、あいまい一致が含まれています。
https://pypi.python.org/pypi/regex/
あいまい一致の構文はかなり表現力豊かに見えますが、これにより、1つ以下の挿入/追加/削除で一致が得られます。
import regex
regex.match('(amazing){e<=1}', 'amaging')
difflib.SequenceMatcher.get_matching_blocks
?
>>> import difflib
>>> large_string = "thelargemanhatanproject"
>>> query_string = "manhattan"
>>> s = difflib.SequenceMatcher(None, large_string, query_string)
>>> sum(n for i,j,n in s.get_matching_blocks()) / float(len(query_string))
0.8888888888888888
>>> query_string = "banana"
>>> s = difflib.SequenceMatcher(None, large_string, query_string)
>>> sum(n for i,j,n in s.get_matching_blocks()) / float(len(query_string))
0.6666666666666666
[〜#〜]更新[〜#〜]
import difflib
def matches(large_string, query_string, threshold):
words = large_string.split()
for Word in words:
s = difflib.SequenceMatcher(None, Word, query_string)
match = ''.join(Word[i:i+n] for i, j, n in s.get_matching_blocks() if n)
if len(match) / float(len(query_string)) >= threshold:
yield match
large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"
print list(matches(large_string, query_string, 0.8))
上記のコード印刷:['manhatan', 'manhattn']
私は fuzzywuzzy を使用して、しきい値に基づいてファジー一致させ、 fuzzysearch を使用して、一致から単語をファジー抽出します。
process.extractBests
は、クエリ、単語のリスト、およびカットオフスコアを受け取り、カットオフスコアを超えるマッチとスコアのタプルのリストを返します。
find_near_matches
はprocess.extractBests
の結果を受け取り、単語の開始インデックスと終了インデックスを返します。インデックスを使用して単語を作成し、作成したWordを使用して大きな文字列内のインデックスを検索します。 max_l_dist
of find_near_matches
は、ニーズに合わせて調整する必要がある「レーベンシュタイン距離」です。
from fuzzysearch import find_near_matches
from fuzzywuzzy import process
large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"
def fuzzy_extract(qs, ls, threshold):
'''fuzzy matches 'qs' in 'ls' and returns list of
tuples of (Word,index)
'''
for Word, _ in process.extractBests(qs, (ls,), score_cutoff=threshold):
print('Word {}'.format(Word))
for match in find_near_matches(qs, Word, max_l_dist=1):
match = Word[match.start:match.end]
print('match {}'.format(match))
index = ls.find(match)
yield (match, index)
テスト;
print('query: {}\nstring: {}'.format(query_string, large_string))
for match,index in fuzzy_extract(query_string, large_string, 70):
print('match: {}\nindex: {}'.format(match, index))
query_string = "citi"
print('query: {}\nstring: {}'.format(query_string, large_string))
for match,index in fuzzy_extract(query_string, large_string, 30):
print('match: {}\nindex: {}'.format(match, index))
query_string = "greet"
print('query: {}\nstring: {}'.format(query_string, large_string))
for match,index in fuzzy_extract(query_string, large_string, 30):
print('match: {}\nindex: {}'.format(match, index))
出力;
クエリ:マンハッタン
string:thelargemanhatanprojectはthemanhattincityの素晴らしいプロジェクトです
一致:マンハタン
インデックス:8
一致:マンハッティン
インデックス:49
クエリ:シティ
string:thelargemanhatanprojectはthemanhattincityの素晴らしいプロジェクトです
一致:都市
インデックス:58
クエリ:あいさつ
string:thelargemanhatanprojectはthemanhattincityの素晴らしいプロジェクトです
一致:すばらしい
インデックス:29
最近、Python用のアライメントライブラリを作成しました: https://github.com/eseraygun/python-alignment
これを使用すると、シーケンスの任意のペアに対して任意のスコアリング戦略を使用して、グローバルアライメントとローカルアライメントの両方を実行できます。実際には、あなたの場合、_query_string
_の部分文字列を気にしないので、セミローカルな配置が必要です。次のコードでは、ローカルアライメントといくつかのヒューリスティックを使用してセミローカルアルゴリズムをシミュレートしましたが、適切な実装のためにライブラリを拡張するのは簡単です。
これは、READMEファイル内のコードの例です。
_from alignment.sequence import Sequence, GAP_ELEMENT
from alignment.vocabulary import Vocabulary
from alignment.sequencealigner import SimpleScoring, LocalSequenceAligner
large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"
# Create sequences to be aligned.
a = Sequence(large_string)
b = Sequence(query_string)
# Create a vocabulary and encode the sequences.
v = Vocabulary()
aEncoded = v.encodeSequence(a)
bEncoded = v.encodeSequence(b)
# Create a scoring and align the sequences using local aligner.
scoring = SimpleScoring(1, -1)
aligner = LocalSequenceAligner(scoring, -1, minScore=5)
score, encodeds = aligner.align(aEncoded, bEncoded, backtrace=True)
# Iterate over optimal alignments and print them.
for encoded in encodeds:
alignment = v.decodeSequenceAlignment(encoded)
# Simulate a semi-local alignment.
if len(filter(lambda e: e != GAP_ELEMENT, alignment.second)) != len(b):
continue
if alignment.first[0] == GAP_ELEMENT or alignment.first[-1] == GAP_ELEMENT:
continue
if alignment.second[0] == GAP_ELEMENT or alignment.second[-1] == GAP_ELEMENT:
continue
print alignment
print 'Alignment score:', alignment.score
print 'Percent identity:', alignment.percentIdentity()
print
_
_minScore=5
_の出力は次のとおりです。
_m a n h a - t a n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889
m a n h a t t - i
m a n h a t t a n
Alignment score: 5
Percent identity: 77.7777777778
m a n h a t t i n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889
_
minScore
引数を削除すると、スコアが最も一致するものだけが取得されます。
_m a n h a - t a n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889
m a n h a t t i n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889
_
ライブラリのすべてのアルゴリズムはO(n * m)
時間の複雑さを持っていることに注意してください。n
およびm
はシーケンスの長さです。
上記のアプローチは良いですが、私は干し草のたくさんの中で小さな針を見つける必要があり、最終的には次のようにアプローチしました:
from difflib import SequenceMatcher as SM
from nltk.util import ngrams
import codecs
needle = "this is the string we want to find"
hay = "text text lots of text and more and more this string is the one we wanted to find and here is some more and even more still"
needle_length = len(needle.split())
max_sim_val = 0
max_sim_string = u""
for ngram in ngrams(hay.split(), needle_length + int(.2*needle_length)):
hay_ngram = u" ".join(ngram)
similarity = SM(None, hay_ngram, needle).ratio()
if similarity > max_sim_val:
max_sim_val = similarity
max_sim_string = hay_ngram
print max_sim_val, max_sim_string
収量:
0.72972972973 this string is the one we wanted to find