古代文人诗词化用——检索两字符串的重复子串

前言:中国古代的文人在吟诗作词时常化用前人的好词佳句,对研究中国古代文学的人来说,可能知道诗人A收到著名诗人B的影响很大,但若把A所有作品中化用B的字段全找出来,不知要来回比对多久。然而没有这一数据,就无法进行后续的分析。皓首穷经,仅仅能研究透几人而已——究其原因,不是水平不够,而是时间有限,机械式的检索工作耗费了学者太多时间精力,需要用技术解放。

1 任务

说人话:将目标诗人A的诗集与参考诗人B的诗集进行比对,得到A诗集中化用B诗集的部分。

技术表述:检测两字符串中的重复子串,其中当连续重复字符长度超过e(化用标准)时,认为是重复子串。

2 步骤

2.1 数据获取

首先从各种古籍数据库获取两人的诗集。由研究者检索、下载得到。将文本转为统一的编码格式,如utf-8,便于计算机处理。

2.2 数据规整

  • 去标点:一般是没有标点的,如果有,全删除,以减少计算量
  • 加分隔:绝大多数文本诗的标题独占一行,并且和正文区分明显,如前有空格,或后有作者等等。为了后续处理能够区分标题与正文,在标题后加入分隔符,如空格。
  • 去换行:绝大多数文本诗的标题独占一行,诗正文或不换行或一句或两句一行。把正文中的换行全删掉。
  • 去除数字、无关字符(如卷XX)、对形如“XXn首…其一…其二…”系列中的各标题进行完善

规整过后的诗集变为类似csv的形式。本文中案例的格式是
题目 正文,如
春晓 春眠不觉晓处处闻啼鸟夜来风雨声花落知多少

数据规整的具体实现因原始数据而异,(多用正则表达式)所以没什么代码好贴的。不过恰恰由于原始数据的文本太多样化,还是免不了人工。这一步是最花时间的。

2.3 文本读取

使用Python(不推荐3以下的版本。对于中文处理太不友好,各种转码搞晕)的字典来存储诗集。

这里就用到了2.2中的分隔符,使用split函数来将每首诗的题目作为字典的key,内容作为value

注意其参数1,只分割一次,避免正文中也出现分隔符导致分成多段无法匹配key,value两个参数的情况。

#从文件中读入诗集的函数
def ReadPoem(fname):
    d = {}
    i = 1
    f = open(fname+'.txt',encoding='utf-8')
    for l in f.readlines():
        try:
            k,v = l.split(' ',1)
        except Exception as e:
            #输出格式有误的诗题,以便整改源数据
            print(l)
        d[str(i)+k.strip()] = v.strip()
        i += 1
    #print(i)
    f.close()
    print(fname+"诗集解析完毕!共有 %d 首诗" % len(d))
    return d

一个诗集文件动辄十几万字,仅靠2.2的数据规整难免有所疏漏,因此这里使用了异常来进一步检查不规整的地方。并且用i给每个诗词编了号,功能有3:

  • 方便后续结果的浏览
  • 辅助检查读入过程有没有遗漏
  • 防止同名诗解析出同样的key导致字典的value被重写

使用ReadPoem()函数,就将A、B的诗集分别读入了字典d1, d2中。

2.4 循环比对

这部分是连续重复字段检索的核心算法。无奈本人Python水平太低,写完之后觉得完全是c的风格,惭愧,凑合看吧。。。

  • 四层循环正文遍历:A的每首诗pa→B的每首诗pb→A诗的每个字wa→B诗的每个字wb,对所有正文进行遍历;
  • 向下窥探重复子串:如果wa==wb,则需要比对下一个wa和下一个wb是否也相等。这样直到重复字段rec_c结束为止;
  • 化用标准满足判断:如果len(rec_c)>用户指定的化用标准重复子串长度e,则rec_c需要记录下来;
  • 跳过已测重复子串:如果检测出了符合标准的连续重复字段,则pa中的这段不需要再检测,所以循环的索引需要往后跳len(rec_c)-1。因为索引改变,所以相关的循环不能用for ... in ...
  • 组装题目和化用串rec_c是单段重复子串,两首诗之间可能会有多个重复子串,使用rec存储两首诗比对出的各rec_c,将两首诗的题目pa,pb和重复内容rec组装起来。

则核心代码如下(部分变量名和上文不同):

#########################循环比对######################
e = 3   #化用标准
num = 0 #进度
l_r = []#结果列表

#遍历A的诗集
for p1 in d1:
    #遍历B的诗集
    for p2 in d2:
        rec = ''
        i = 0
        #遍历A诗的正文        
        while i < len(d1[p1]):
            j = 0
            #遍历B诗的正文            
            while j < len(d2[p2]):
                rec_c = ''#两首诗中所有的重复子串
                if d1[p1][i] == d2[p2][j]:#匹配到了1个相同的字
                    rec_c += d1[p1][i]
                    #往下窥探直到没有连续相同的字
                    for k in range(1,min(len(d1[p1])-i,len(d2[p2])-j)):
                        #记录重复子串
                        if d1[p1][i+k] == d2[p2][j+k]:
                            rec_c += d1[p1][i+k]
                        #不再有连续相同的字,中断循环
                        else:
                            break
                    #跳过已检测出的重复子串
                    i += (len(rec_c)-1)
                    j += (len(rec_c)-1)
                #重复子串长度>=e,将重复子串加入rec,以顿号相连
                if len(rec_c) >= e:
                    rec = rec + rec_c + '、'
                j += 1
            i += 1
        #记录化用情况
        if len(rec)>0:
            l_r.append(p1+ ' ------ ' + p2 + ':' + rec[:-1] + '\r\n')
    #显示进度
    num += 1
    print("%d / %d 已完成" % (num,len(d1)))

代码中的记录化用情况部分对结果进行了组装,得到形如
A某首诗的标题 ------ B某首诗的标题:重复子串1、重复子串2、...
形式的结果表达。

最后再用sort结合lambda表达式对结果排序,将重复串长的放前边。
sort(l_r, lambda x:len(rec.split(':',1)[1]), reverse=True)

将结果列表l_r写入文件(注意加上参数encoding='utf-8',不然win默认gbk可能无法编码解码),就得到了诗词化用分析的结果。

将上述代码封装一下,使用一个列表存储各诗人的名字,从而实现批量比对。这时候自己就可以喝个咖啡看个电影健个身咯~

3 讨论

不论是算法还是代码应该都有很大的改进空间。使用本文中的程序分析11万字和13万字的两个文本,笔记本i7单核用了大概70±10min左右(具体数值忘了记录)。虽然时间不算短,但这个体量的古文献如果靠学者自己比对,那就得“髮稀目涩光陰短,衣帶漸寬人憔悴 ”了!

虽然没有使用大数据和NLP相关的技术(中文+古文+诗词,这也很难用NLP搞吧),智能性有待提高,但这种程度的自动化就已经大大简化了研究者的工作,并且在古代文人诗词化用检索这个方向上具有一定的通用性。理工科的技术引入文学研究领域也是很有必要的呢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值