【Python Tips】文本预处理:彻底搞懂Difflib——Python自带文本序列比较差异处理库

一、Difflib简介

        Difflib 是 Python 标准库中的一个模块,用于比较文本的差异。它可以生成文本之间的差异报告。这个库可以用来比较字符串、序列等,还提供了一些实用的工具来显示差异。

        在涉及到两个字符串进行差异字比较、信息合并成完整句、相同字提取等任务时,使用Difflib是一个不错的选择。

        本文结尾处存放所有代码,方便取用。

二、Difflib使用教程

        1.从一个例子出发

        从一个例子出发去看这个封装好的函数是一个很好的起点,通过看其对样例的操作结果就可以知道基本的使用逻辑了。

        其中text_total理解为完整信息的句子(对应理想中我们想获取的结果),text_a 和 text_b 是完整句中的部分信息(对应现实中残缺的信息)。我们想通过Difflib函数提取 text_a 和 text_b 中公共部分(视为可信度很高的信息),以某一个残缺为基础,补足为完整信息。

# 文本样例
text_total = list('从零基础开始学习机器学习和深度学习')
text_a = list('从零开始学机器学习度学习')
text_b = list('零基础习机器学习和深度学习')
print(text_a, text_b, text_total)

        下图展示了这三句话对应字的对齐关系,可以更好的看到示例数据结构。其中红框代表公共可信部分,蓝框黄框分别代表各自独有部分。

        2.判断相似度

        首先需要一个判断,这可以看 text_a 和 text_b 中是否有需要的有用的信息,也可以做一个基本判断——text_a 和 text_b是否存在公共部分,当两个序列完全不同时得到结果 0 ,完全相同1。

import difflib

# difflib主类——text_a和text_b公共有无
matcher = difflib.SequenceMatcher(None, text_a, text_b)

# 返回两个文本序列相似度
similarity_ab = matcher.ratio()
print(f"a与b文本相似度:{similarity_ab:.2f}")
## a与b文本相似度:0.64

# set_seq可以重新设置替换要比较文本1 或 2
matcher.set_seq2(text_total)    # 替换text_b,比较text_a与text_total
similarity_a_t = matcher.ratio()
print(f"a与total文本相似度:{similarity_a_t:.2f}")
## a与total文本相似度:0.83

matcher.set_seq1(text_b)
matcher.set_seq2(text_total)    # 替换text_a,比较text_b与text_total
similarity_b_t = matcher.ratio()
print(f"b与total文本相似度:{similarity_b_t:.2f}")
## b与total文本相似度:0.87

        3.匹配相同的字

        difflib.SequenceMatcher是difflib中关键的主类,很多功能函数都在此基础下实现。下面实现匹配两个字串中公共部分的功能。

# 重新设置比较的序列
matcher.set_seq1(text_a)
matcher.set_seq2(text_b)

# 找到相同块并输出索引位置
matching_blocks = matcher.get_matching_blocks()

print("匹配的相同字及其在两个子串中的索引:")
for block in matching_blocks:
    i1, i2, size = block
    if size > 0:
        print(f"有{size}个匹配, 子串1的索引 {i1} 到 {i1 + size} 与 子串2的索引 {i2} 到 {i2 + size} 匹配")
        print(f"匹配字:'{text_a[i1:i1 + size]}'")


## 输出结果
匹配的相同字及其在两个子串中的索引:
有1个匹配, 子串1的索引 1 到 2 与 子串2的索引 0 到 1 匹配
匹配字:'['零']'
有4个匹配, 子串1的索引 5 到 9 与 子串2的索引 4 到 8 匹配
匹配字:'['机', '器', '学', '习']'
有3个匹配, 子串1的索引 9 到 12 与 子串2的索引 10 到 13 匹配
匹配字:'['度', '学', '习']'

        比较示例数据,确实将相同的红色部分找到了。

        4.差异报告

        Difflib还可以提供关于这两个序列更详细的信息,具体来说,首先要有一个基准,一般是输入的第一个序列,即 text_a。对应第二个序列,difflib给出了4种对应关系

        equal 代表两个序列相等的部分

        delete :表示第一个序列(text_a)有,另一个没有的——需要从基准序列删除才会使得两序列相等的元素

        insert :和delete相反,表示第一个序列(text_a)没有,另一个有的——基准需添加此以元素才会相等

        replace :这个比较复杂,代表需要从基准(text_a)中替换的部分,下面图示可以看出replace和delete与insert的区别

        可以简单理解为replace是最混乱的区域——它可能是完全错位(如此例),也可能是某一个序列里包含错误的误导信息

opcodes = matcher.get_opcodes()
for tag, i1, i2, j1, j2 in opcodes:
    if tag == 'equal':
        # 如果是相同的部分
        print(f"{tag}: text_a[{text_a[i1:i2]}] == text_b[{text_b[j1:j2]}]")
    elif tag == 'replace':
        # 如果是替换的部分
        print(f"{tag}: text_a[{text_a[i1:i2]}] = text_b[{text_b[j1:j2]}]")
    elif tag == 'delete':
        # 如果是删除的部分,只添加子串1的内容
        print(f"{tag}: text_a[{text_a[i1:i2]}]")
    elif tag == 'insert':
        # 如果是插入的部分,只添加子串2的内容
        print(f"{tag}: text_b[{text_b[j1:j2]}]")


## 输出结果
delete: text_a[['从']]
equal: text_a[['零']] == text_b[['零']]
replace: text_a[['开', '始', '学']] = text_b[['基', '础', '习']]
equal: text_a[['机', '器', '学', '习']] == text_b[['机', '器', '学', '习']]
insert: text_b[['和', '深']]
equal: text_a[['度', '学', '习']] == text_b[['度', '学', '习']]

        上述所有完整代码如下:

import difflib
# 文本样例
text_total = list('从零基础开始学习机器学习和深度学习')
text_a = list('从零开始学机器学习度学习')
text_b = list('零基础习机器学习和深度学习')

# difflib主类——text_a和text_b公共有无
matcher = difflib.SequenceMatcher(None, text_a, text_b)

# 返回两个文本序列相似度
similarity_ab = matcher.ratio()
print(f"a与b文本相似度:{similarity_ab:.2f}")
## a与b文本相似度:0.64

# set_seq可以重新设置替换要比较文本1 或 2
matcher.set_seq2(text_total)    # 替换text_b,比较text_a与text_total
similarity_a_t = matcher.ratio()
print(f"a与total文本相似度:{similarity_a_t:.2f}")
## a与total文本相似度:0.83

matcher.set_seq1(text_b)
matcher.set_seq2(text_total)    # 替换text_a,比较text_b与text_total
similarity_b_t = matcher.ratio()
print(f"b与total文本相似度:{similarity_b_t:.2f}")
## b与total文本相似度:0.87

# 重新设置比较的序列
matcher.set_seq1(text_a)
matcher.set_seq2(text_b)

# 找到相同块并输出索引位置
matching_blocks = matcher.get_matching_blocks()

print("匹配的相同字及其在两个子串中的索引:")
for block in matching_blocks:
    i1, i2, size = block
    if size > 0:
        print(f"有{size}个匹配, 子串1的索引 {i1} 到 {i1 + size} 与 子串2的索引 {i2} 到 {i2 + size} 匹配")
        print(f"匹配字:'{text_a[i1:i1 + size]}'")

opcodes = matcher.get_opcodes()
for tag, i1, i2, j1, j2 in opcodes:
    if tag == 'equal':
        # 如果是相同的部分
        print(f"{tag}: text_a[{text_a[i1:i2]}] == text_b[{text_b[j1:j2]}]")
    elif tag == 'replace':
        # 如果是替换的部分
        print(f"{tag}: text_a[{text_a[i1:i2]}] = text_b[{text_b[j1:j2]}]")
    elif tag == 'delete':
        # 如果是删除的部分,只添加子串1的内容
        print(f"{tag}: text_a[{text_a[i1:i2]}]")
    elif tag == 'insert':
        # 如果是插入的部分,只添加子串2的内容
        print(f"{tag}: text_b[{text_b[j1:j2]}]")
  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值