查找最佳子字符串匹配
问题描述
我正在寻找使用现有库(difflib
、fuzzywuzzy
、python-levenshtein
)的库或方法,以便在文本(corpus
)中查找字符串(query
)的最匹配项
我开发了一个基于difflib
的方法,将corpus
拆分成大小为n
(长度为query
)的ngram。
import difflib
from nltk.util import ngrams
def get_best_match(query, corpus):
ngs = ngrams( list(corpus), len(query) )
ngrams_text = [''.join(x) for x in ngs]
return difflib.get_close_matches(query, ngrams_text, n=1, cutoff=0)
当查询和匹配字符串之间的差异只是字符替换时,它可以按我希望的方式工作。
query = "ipsum dolor"
corpus = "lorem 1psum d0l0r sit amet"
match = get_best_match(query, corpus)
# match = "1psum d0l0r"
但如果区别是字符删除,则不是。
query = "ipsum dolor"
corpus = "lorem 1psum dlr sit amet"
match = get_best_match(query, corpus)
# match = "psum dlr si"
# expected_match = "1psum dlr"
有没有办法获得更灵活的结果大小(如expected_match
)?
编辑%1:
- 此脚本的实际用途是将查询(字符串)与 OCR输出混乱。
- 正如我在问题中所说,OCR可能会混淆字符,甚至会遗漏字符。
- 如果可能,还要考虑单词之间缺少空格的情况。
- 最佳匹配是指不包括来自查询上的字符以外的其他单词的字符。
编辑2:
我现在使用的解决方案是使用(n-k)-grams for k = {1,2,3}
扩展ngram以防止3次删除。它比第一个版本好得多,但在速度方面效率不高,因为我们要检查的ngram数量是第一个版本的3倍多。它也是一个不可概括的解决方案。
解决方案
此函数查找可变长度的最佳匹配子字符串。
该实现将语料库视为一个长字符串,从而避免您担心空格和未分隔的单词。
代码摘要:
1.以step
的步长扫描语料库中的匹配值,以查找最高匹配值的大致位置pos
。
2.通过调整子字符串的左/右位置,查找pos
附近匹配值最高的子字符串。
from difflib import SequenceMatcher
def get_best_match(query, corpus, step=4, flex=3, case_sensitive=False, verbose=False):
"""Return best matching substring of corpus.
Parameters
----------
query : str
corpus : str
step : int
Step size of first match-value scan through corpus. Can be thought of
as a sort of "scan resolution". Should not exceed length of query.
flex : int
Max. left/right substring position adjustment value. Should not
exceed length of query / 2.
Outputs
-------
output0 : str
Best matching substring.
output1 : float
Match ratio of best matching substring. 1 is perfect match.
"""
def _match(a, b):
"""Compact alias for SequenceMatcher."""
return SequenceMatcher(None, a, b).ratio()
def scan_corpus(step):
"""Return list of match values from corpus-wide scan."""
match_values = []
m = 0
while m + qlen - step <= len(corpus):
match_values.append(_match(query, corpus[m : m-1+qlen]))
if verbose:
print(query, "-", corpus[m: m + qlen], _match(query, corpus[m: m + qlen]))
m += step
return match_values
def index_max(v):
"""Return index of max value."""
return max(range(len(v)), key=v.__getitem__)
def adjust_left_right_positions():
"""Return left/right positions for best string match."""
# bp_* is synonym for 'Best Position Left/Right' and are adjusted
# to optimize bmv_*
p_l, bp_l = [pos] * 2
p_r, bp_r = [pos + qlen] * 2
# bmv_* are declared here in case they are untouched in optimization
bmv_l = match_values[p_l // step]
bmv_r = match_values[p_l // step]
for f in range(flex):
ll = _match(query, corpus[p_l - f: p_r])
if ll > bmv_l:
bmv_l = ll
bp_l = p_l - f
lr = _match(query, corpus[p_l + f: p_r])
if lr > bmv_l:
bmv_l = lr
bp_l = p_l + f
rl = _match(query, corpus[p_l: p_r - f])
if rl > bmv_r:
bmv_r = rl
bp_r = p_r - f
rr = _match(query, corpus[p_l: p_r + f])
if rr > bmv_r:
bmv_r = rr
bp_r = p_r + f
if verbose:
print("
" + str(f))
print("ll: -- value: %f -- snippet: %s" % (ll, corpus[p_l - f: p_r]))
print("lr: -- value: %f -- snippet: %s" % (lr, corpus[p_l + f: p_r]))
print("rl: -- value: %f -- snippet: %s" % (rl, corpus[p_l: p_r - f]))
print("rr: -- value: %f -- snippet: %s" % (rl, corpus[p_l: p_r + f]))
return bp_l, bp_r, _match(query, corpus[bp_l : bp_r])
if not case_sensitive:
query = query.lower()
corpus = corpus.lower()
qlen = len(query)
if flex >= qlen/2:
print("Warning: flex exceeds length of query / 2. Setting to default.")
flex = 3
match_values = scan_corpus(step)
pos = index_max(match_values) * step
pos_left, pos_right, match_value = adjust_left_right_positions()
return corpus[pos_left: pos_right].strip(), match_value
示例:
query = "ipsum dolor"
corpus = "lorem i psum d0l0r sit amet"
match = get_best_match(query, corpus, step=2, flex=4)
print(match)
('i psum d0l0r', 0.782608695652174)
一些好的启发式建议是始终保留step < len(query) * 3/4
和flex < len(query) / 3
。我还增加了区分大小写的功能,以防这很重要。当您开始使用步长和伸缩值时,它可以很好地工作。步长值越小,结果越好,但计算时间越长。Flex控制允许结果子字符串的长度的灵活性。
重要说明:这将只找到第一个最佳匹配,因此如果有多个同样好的匹配,则只会返回第一个。若要允许多个匹配,请更改index_max()
以返回输入列表的n
最高值的索引列表,并循环该列表中的值的adjust_left_right_positions()
。
相关文章