web-dev-qa-db-ja.com

Pythonで長い文字列に存在するファジー/近似部分文字列をチェックしていますか?

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)>
42
DhruvPathak

Reを置き換える予定の新しい正規表現ライブラリには、あいまい一致が含まれています。

https://pypi.python.org/pypi/regex/

あいまい一致の構文はかなり表現力豊かに見えますが、これにより、1つ以下の挿入/追加/削除で一致が得られます。

import regex
regex.match('(amazing){e<=1}', 'amaging')
14
mgbelisle

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

19
falsetru

私は fuzzywuzzy を使用して、しきい値に基づいてファジー一致させ、 fuzzysearch を使用して、一致から単語をファジー抽出します。

process.extractBestsは、クエリ、単語のリスト、およびカットオフスコアを受け取り、カットオフスコアを超えるマッチとスコアのタプルのリストを返します。

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

15
Nizam Mohamed

最近、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はシーケンスの長さです。

12
Eser Aygün

上記のアプローチは良いですが、私は干し草のたくさんの中で小さな針を見つける必要があり、最終的には次のようにアプローチしました:

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