生成正则表达式--阿里笔试语义匹配题的思路

这里写自定义目录标题

问题描述

在Q群里聊到关于规则生成的问题,有个这样的题目:

在基于自然语言的人机交互系统中,通常会定义一些语义模板来训练NLU (自然语言理解)模型,比如下面的模板可以支持用户通过语音控制机器播放音乐:

  1. 放几首@{singer}的歌
  2. 播放一首@{singer}的歌
  3. 来一曲@{singer}的歌曲
  4. 来首@{singer}的音乐
  5. 来个@{singer}的流行音乐

其中"@{singer}"是个参数, 代表歌手,比如第一个模板可以匹配这样的用户query:“放几首刘德华的歌”。可以看到,同样是放歌,有很多种不同但相似的说法,但把他们一条一条单独列出来,编辑的成本会比较高, 而且会漏掉一些说法, 不严谨。实际上,上面的5个模板,可以用下面的语义模板表达式来表示:

<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>

其中包含中括号("[]") 、尖括号("<>") 和竖线("|")三种元素:

  • 中括号代表其中的内容是可选的,比如"[歌]曲",能匹配"歌"和"歌曲";
  • 尖括号代表其中的内容是必选的,比如"<播>放", 能匹配"播放";
  • 括号是可以嵌套的;
  • 竖线代表或的关系,即竖线分隔的内容是可替换的,
  • 比如"<播放|来首>歌曲"能匹配"播放歌曲"和"来首歌曲",再如"[播放|来首]歌曲"能匹配"播放歌曲",“来首歌曲"和"歌曲”(因为中括号里面是可选的,所以可以匹配歌曲) ;
  • 竖线在其所属的括号内,优先级大于括号中的其他括号,比如"<[播]放|来>首歌曲",能匹配"播放首歌曲",“放首歌曲"和"来首歌曲";
  • 竖线可以脱离括号独立存在,比如"在哪里|哪里有",可以匹配"在哪里"和"哪里有";

那么,给一个上述的语义模板表达式和用户的query,你能判断用户的quey是否能匹配这个表达式吗?

原文链接:阿里笔试语义匹配题

思路

一看这规则,我就想到了正则,真TMD像啊。有正则这么好的东西,难道还要再造个轮子么,肯定不要了。直接把规则“转换”成一个正则表达式,不就可以了么?

题目中提供的规则已经写得这么规整了,基本上只需要替换一下就可以了:

#根据文本规则生成正则表达式
#parm:
#    txt    用户的规则
#返回:
#    字符串,生成的正则表达式;
#测试用例:
# genRegex('<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>')
#return:
#   ^((播)?放|来)(一|几)?(首|曲|个)@\{singer\}的(歌(曲)?|(流行)?音乐)$ 
def genRegex (txt):
    pass
    ret = txt
    ret = ret.replace('{','\{')
    ret = ret.replace('}','\}')
    ret = ret.replace('[','(')
    ret = ret.replace(']',')?')
    ret = ret.replace('<','(')
    ret = ret.replace('>',')')
    ret = '^' + ret + '$'
    return ret

不要怀疑,就是这么简单!
根据题目的意思,这里我把 <> 替换成了正则中的 () ,而 [] 替换成了正则中的 ()?
生成了正则表达式后,就可以用正则表达式来判断了,代码也很简单:


#对单条文本判断是否匹配规则
#参数:
#   strRegex    string,正则表达式
#   strTxt      string,待匹配的单条文本
#返回:
#   匹配返回True ,否则返回False
def testRule (strRegex,strTxt):
    pass
    try:
        #编译正则对象,参数:单行,多行,大小写
        reObj = re.compile(strRegex,re.S|re.U|re.M)
        searchObj = re.match(reObj,strTxt)
        #print(searchObj)
        return searchObj != None
    except Exception as e :
        return False

测试

最后,找几个句子来测试一下:

def doctest ():
    txt = [
    '放几首@{singer}的歌',
    '播放一首@{singer}的歌',
    '来一曲@{singer}的歌曲',
    '来首@{singer}的音乐',
    '来个@{singer}的流行音乐',
    '放首@{singer}的歌',
    '播放@{singer}的歌',
    '在哪里可以找到@{singer}的音乐',
    '哪里有@{singer}的歌曲',
    ]
    print('批量测试'.center(30,'-') )
    ruleTxt = '<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>'
    ruleRegex = genRegex(ruleTxt)
    print('规则文本:%s \n生成正则表达式:%s ' % ( ruleTxt,ruleRegex))
    print('-'*30)
    print('规则测试:')
    for x in txt:
        pass
        print('测试句子: %s 判断结果:%s' %(x , testRule(ruleRegex,x)) )        

运行结果

调用下方法,测试一下结果:

if __name__ == '__main__':
    pass
    ruleTxt = '<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐>'
    ruleRegex = genRegex(ruleTxt)
    print(ruleRegex)
    print(testRule (ruleRegex, '放几首@{singer}的歌'))

    doctest()

运行结果:

---------- python36 ----------
^((播)?放|来)(一|几)?(首|曲|个)@\{singer\}的(歌(曲)?|(流行)?音乐)$
True
-------------批量测试-------------
规则文本:<[播]放|来>[一|几]<首|曲|个>@{singer}的<歌[曲]|[流行]音乐> 
生成正则表达式:^((播)?放|来)(一|几)?(首|曲|个)@\{singer\}的(歌(曲)?|(流行)?音乐)$ 
------------------------------
规则测试:
测试句子: 放几首@{singer}的歌 判断结果:True
测试句子: 播放一首@{singer}的歌 判断结果:True
测试句子: 来一曲@{singer}的歌曲 判断结果:True
测试句子: 来首@{singer}的音乐 判断结果:True
测试句子: 来个@{singer}的流行音乐 判断结果:True
测试句子: 放首@{singer}的歌 判断结果:True
测试句子: 播放@{singer}的歌 判断结果:False
测试句子: 在哪里可以找到@{singer}的音乐 判断结果:False
测试句子: 哪里有@{singer}的歌曲 判断结果:False

输出完成 (耗时: 0 秒)

完整源码

“不管白猫黑猫,能抓老鼠就是好猫”,思路比较土,但能解决问题就好。
不足之处,还请指正。

完整代码放在这里:
代码下载

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值