python字符串模糊匹配,并计算匹配分数

这篇具有很好参考价值的文章主要介绍了python字符串模糊匹配,并计算匹配分数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、thefuzz

thefuzz包以前叫fuzzywuzzy,0.19版本开始改名为thefuzz,github地址:

GitHub - seatgeek/thefuzz: Fuzzy String Matching in Python

可以通过命令pip install thefuzz安装此包。用法还是比较简单的:

from thefuzz import fuzz

fuzz.ratio("test", "test!")

>>89

上面两个字符串的相似度为89%。

二、相似度ratio的计算

我们先看看这个包下面的源码,来查看thefuzz是怎么实现模糊匹配的。thefuzz源码包的结构如下:

python字符串模糊匹配,并计算匹配分数

先看看ratio方法源码:

def ratio(s1, s2):
    s1, s2 = utils.make_type_consistent(s1, s2)

    m = SequenceMatcher(None, s1, s2)
    return utils.intr(100 * m.ratio())

 可以看到,ratio方法用到了一个比较关键的类SequenceMatcher,但是这个类却有可能来自两个不同的地方。

2.1 SequenceMatcher的来源

看看fuzzy.py的头部代码:

import platform
import warnings

try:
    #从当前文件夹的StringMatcher中导入StringMatcher
    from .StringMatcher import StringMatcher as SequenceMatcher
except ImportError:
    if platform.python_implementation() != "PyPy":
        warnings.warn('Using slow pure-python SequenceMatcher. Install python-Levenshtein to remove this warning')
    from difflib import SequenceMatcher

#导入当前文件夹的utils包,.代表当前目录
from . import utils

上面代码涉及了一个导入问题,即先从当前文件StringMatcher中导入StringMatcher,如果导入出现异常,就去difflib中导入SequenceMatcher。

正如上面第一张图中看到的,当然文件夹下面确实有一个叫StringMatcher.py的文件,也看看它前面的代码:

from Levenshtein import *
from warnings import warn

class StringMatcher:
.............
.............

可以看出,这个StringMatcher类引用了Levenshtein包,这个包也是用来计算字符串模糊匹配的,效率上来说,有可能比difflib中的SequenceMatcher快4-10倍。

Levenshtein包是用C语言写的,比较复杂,最初的项目地址:

GitHub - miohtama/python-Levenshtein: The Levenshtein Python C extension module contains functions for fast computation of Levenshtein distance and string similarity

后来这个作者没有维护了,然后由另一个在维护,项目的地址在这里:

GitHub - ztane/python-Levenshtein: The Levenshtein Python C extension module contains functions for fast computation of Levenshtein distance and string similarity

但是,这个页面上,作者也说了,他也7年不维护了,现在没找到新的维护者。

不管怎么说,如果要使用Levenshtein,还是可以安装的:

pip install python-Levenshtein

总结就是,thefuzz有两种实现方式,一种是依赖difflib,另一种依靠 python-Levenshtein。先看简单的difflib。

2.2 difflib包中的SequenceMatcher

首先导入:

from difflib import SequenceMatcher

这个类的主要作用是计算两个匹配字符串的相似度,如下:

s = SequenceMatcher(None, "abcde", "bcde")
s.ratio()

输出值为0.888888。这个是怎么计算的呢?可以查看difflib.py的源代码(我的电脑在D:\ProgramData\Miniconda3\Lib目录下),如下:

def ratio(self):
    matches = sum(triple[-1] for triple in self.get_matching_blocks())
    return _calculate_ratio(matches, len(self.a) + len(self.b))

这个方法涉及到两个比较重要的方法,一个是get_matching_blocks(),这个方法用于获取匹配的字符块。另一个方法_calculate_ratio,用于计算相似度,先看_calculate_ratio,代码如下:

def _calculate_ratio(matches, length):
    if length:
        return 2.0 * matches / length
    return 1.0

上面代码的第三行是关键,matches表示的字符个数,length是两个字符串加起来的总长度。如上面的"abcde"和 "bcde",ratio的计算方法就是2*4/9,即8/9=0.888888。

再看看get_matching_blocks方法。这个方法比较复杂,我们先来看下,这个方法的用法:

s = SequenceMatcher(None, "abchde", "bcde")
print(s.get_matching_blocks())

输出如下:

[Match(a=1, b=0, size=2), Match(a=4, b=2, size=2), Match(a=6, b=4, size=0)] 

什么意思?从方法的名字大概就能看出来,就是获得匹配的所有字符块。上面的代码输出了3个Match对象,Match(a=1, b=0, size=2)的意思是"abchde"从索引1(a=1)开始,"bcde"从索引0(b=0)开始,匹配到2(size=2)个相等字符,即“bc”。

最后一个Match(a=6, b=4, size=0)是固定的,a、b代表两个字符串的长度,size=0固定不变。用代码描述如下:

(len(a), len(b), 0)

但是如果前面字串符已经匹配过,就不会再进行匹配了,如下:

s = SequenceMatcher(None, "bc", "abchdebc")
print(s.get_matching_blocks()) 

输出:

 [Match(a=0, b=1, size=2), Match(a=2, b=8, size=0)]

即“bc”只匹配了第一次的位置,后面就算出现和它一样的字符串,也不再进行匹配。 

三、process模块

从第一张图中可以看到,除了fuzz.py这个文件,还有一个叫process.py的文件,process模块常用的是从候选列表中,返回与目标字符串最相似的一个结果。来看一个简单的例子:

from thefuzz import fuzz,process

choices = ["hello world", "hello china", "hello beijing"]
print(process.extractOne("china",choices))

#输出内容
>>('hello china', 90)

正如上面代码所示,process最常用的用法是从众多字符串中,找到最佳匹配的字符串。

process.extractOne的格式如下:

extractOne(query, choices, processor=default_processor, scorer=default_scorer, score_cutoff=0):

"""
Args:
    query: A string to match against
    choices: A list or dictionary of choices, suitable for use with extract().
    processor: Optional function for transforming choices before matching.
    scorer: Scoring function for extract().
    score_cutoff: Optional argument for score threshold. If the best
            match is found, but it is not greater than this number, then
            return None anyway ("not a good enough match").  Defaults to 0.

Returns:
    A tuple containing a single match and its score, if a match
    was found that was above score_cutoff. Otherwise, returns None.
"""

query:查询的字符串;

choices: 待匹配的字符串列表或者字典;

processor:可选参数,转换器,在匹配前先对choices进行转换处理;

scorer:可选参数,分数器,用于计算分数;

score_cutoff:可选参数,这个参数的作用是设置一个分数门槛(默认为0),如果小于这个分数,就不返回匹配的字符串,而是返回一个None。

extractOne返回的结果是一个tuple元组(最佳匹配结果,分数)。

我们比较关心的一个问题是,这个分数是怎么计算的?看看下面例子:

from thefuzz import fuzz,process

print(fuzz.ratio("china","hello china"))

choices = ["hello world", "hello china", "hello beijing"]
print(process.extractOne("china",choices))

#输出内容
>>62
>>('hello china', 90)

可以看出,fuzz.ratio与process.extractOne分数的计算方式不一样(一个是62分,一个90分)。fuzz.ratio的计分方式,上面已经讲了,下面来看看extractOne的计分方式。

extractOne的源码如下:

def extractOne(query, choices, processor=default_processor, scorer=default_scorer, score_cutoff=0):
    best_list = extractWithoutOrder(query, choices, processor, scorer, score_cutoff)
    try:
        return max(best_list, key=lambda i: i[1])
    except ValueError:
        return None

我们刚才说了,第三个参数scorer是用于计分的,它的默认值为default_scorer,那我们先找到这个default_scorer的值:

default_scorer = fuzz.WRatio

即默认的计分方式为fuzz.WRatio,那么我们回到fuzz.py中,看看WRatio是做什么的?

from thefuzz import fuzz

default_scorer = fuzz.WRatio
default_scorer("china", "hello china")

#输出
>> 90

 可以看出,WRatio的计分方式确实和上面的extractOne相同,都是90分。WRatio的计分方式比较复杂,涉及到一个权重(weight)的概念,它是基于fuzz.ratio()的基础上,做了进一步的校正。

如果我们不想采用WRatio的计分方式,或者想采用fuzz.ratio()的计分方式来提取最佳匹配结果,可以这样:

from thefuzz import fuzz,process

print(fuzz.ratio("china","hello china"))

choices = ["hello world", "hello china", "hello beijing"]
print(process.extractOne("china",choices,scorer=fuzz.QRatio))

#输出
>>62
>>('hello china', 62)

上面代码的计分结果都是62分,因为fuzz.QRatio的内部,除了对参数进行了一些简单的处理以外,直接调用fuzz.ratio()方法返回了结果。所以fuzz.QRatio和fuzz.ratio()的计分方式完全相同。

fuzz.QRatio源代码:

# q is for quick
def QRatio(s1, s2, force_ascii=True, full_process=True):

    if full_process:
        p1 = utils.full_process(s1, force_ascii=force_ascii)
        p2 = utils.full_process(s2, force_ascii=force_ascii)
    else:
        p1 = s1
        p2 = s2

    if not utils.validate_string(p1):
        return 0
    if not utils.validate_string(p2):
        return 0

    return ratio(p1, p2)

通过上面的例子可以看出,如果我们对QRatio、WRatio这些计分方式不满意的话,完全可以自己实现了一个Ratio,将它做为extractOne的参数,实现定制的返回结果。文章来源地址https://www.toymoban.com/news/detail-422692.html

到了这里,关于python字符串模糊匹配,并计算匹配分数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【字符串匹配】暴力匹配算法

    ​ 暴力匹配算法,也称为朴素字符串匹配算法,是一种简单但不高效的字符串匹配方法。它的原理非常直观,其主要思想是逐个字符地比较文本串和模式串,从文本串的每个可能的起始位置开始,依次检查是否有匹配的子串。以下是暴力匹配算法的详细原理: 1. 一个字一个

    2024年02月09日
    浏览(53)
  • 【数据结构】数组和字符串(十四):字符串匹配1:朴素的模式匹配算法(StringMatching)

      字符串(String)是由零个或多个字符(char)顺序排列组成的有限序列,简称为串。例如 “good morning”就是由12个字符构成的一个字符串。一般把字符串记作: S = ′ ′ a 0 a 1 … a n − 1 ′ ′ S=\\\'\\\'a_{0} a_{1}…a_{n-1}\\\'\\\' S = ′′ a 0 ​ a 1 ​ … a n − 1 ′′ ​   其中S是串名,引号中

    2024年02月05日
    浏览(74)
  • Python计算字符串长度的函数

    1、使用内置函数len 这是Python中一种常用的函数,主要功能就是对字符串的长度进行统计,最后会返回一个字符串的实际长度,使用方法如下: 在示例中str就是一个要计算的字符串,它还可以是列表或者是字典等等。 2、使用for循环 使用for循环来统计字符串的长度时,我们可以

    2024年02月13日
    浏览(54)
  • 字符串查找匹配算法

    字符串匹配(查找)是字符串的一种基本操作:给定带匹配查询的文本串S和目标子串T,T也叫做模式串。在文本S中找到一个和模式T相符的子字符串,并返回该子字符串在文本中的位置。 Brute Force Algorithm,也叫朴素字符串匹配算法,Naive String Matching Algorithm。 基本思路就是将

    2024年02月14日
    浏览(58)
  • 【kmp算法】字符串匹配

    kmp算法解决的是字符串匹配的问题,具体来说假定我们要在主串s[ ] 中匹配模式串p[ ],找到匹配到的位置loc; 最自然的想法是暴力写法 (BF)枚举主串字符s[ i ] ,和模式串p[ j ]。一个一个匹配,如果匹配失败,i指针回退回起点,往前进一位,再次进行比较,知道匹配成功。

    2024年02月04日
    浏览(76)
  • 字符串匹配-KMP算法

    KMP算法,字符串匹配算法,给定一个主串S,和一个字串T,返回字串T与之S匹配的数组下标。 在学KMP算法之前,对于两个字符串,主串S,和字串T,我们根据暴力匹配,定义两个指针,i指向主串S的起始,j指向字串T的起始,依次比较,如果 主串i位置的值等于子串j位置的值,

    2024年02月14日
    浏览(55)
  • 字符串匹配算法:KMP

    Knuth–Morris–Pratt(KMP)是由三位数学家克努斯、莫里斯、普拉特同时发现,所有人们用三个人的名字来称呼这种算法,KMP是一种改进的字符串匹配算法,它的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。它的时间复杂度是 O(m+n) 字

    2024年02月06日
    浏览(62)
  • 一些常见的字符串匹配算法

    作者:京东零售 李文涛 字符串匹配在文本处理的广泛领域中是一个非常重要的主题。字符串匹配包括在文本中找到一个,或者更一般地说,所有字符串(通常来讲称其为模式)的出现。该模式表示为p=p[0..m-1];它的长度等于m。文本表示为t=t[0..n-1],它的长度等于n。两个字符串都建

    2023年04月25日
    浏览(43)
  • 动态规划--通配字符串匹配

    1. 题目来源 链接:通配符匹配 来源:LeetCode 2. 题目说明 给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。 ‘?’ 可以匹配任何单个字符。 ‘*’ 可以匹配任意字符串(包括空字符串)。 两个字符串完全匹配才算匹配成功。 说明: s 可能为

    2024年02月14日
    浏览(52)
  • Python三种计算字符串长度的函数分享

      Python三种计算字符串长度的函数 1、使用内置函数len 这是Python中一种常用的函数,主要功能就是对字符串的长度进行统计,最后会返回一个字符串的实际长度,使用方法如下: 1 2 str = \\\"hello python\\\" print ( len ( str )) 在示例中str就是一个要计算的字符串,它还可以是列表或者是字

    2024年02月04日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包