jieba源碼研讀筆記(四) - 正則表達式

前言

jieba包含的三大功能:分詞、詞性標注及關鍵詞提取都需要用到正則表達式。
筆者將jieba/__init__.pyjieba/finalseg/__init__.pyjieba/posseg/__init__.py三個檔案裡出現的正則表達式集中在本篇介紹。

先介紹下筆者在學習正則表達式時用到的工具:
regex101:可以使用這個線上工具來測試regex的效果。
以及Python正則表達式的教學:
Python - Regular Expressions
還有一篇中文的:
正则表达式的功法大全,做NLP再也不怕搞不定字符串了

jieba/init.py

這裡定義了數個正則表達式,它們會在分詞及載入字典時發揮作用。

re_userdict

re_userdict是在解析dict.txt的內容時使用的。
以下是dict.txt的部份內容:

AT&T 3 nz
B超 3 n
c# 3 nz
C# 3 nz
c++ 3 nz
C++ 3 nz

re_userdict = re.compile('^(.+?)( [0-9]+)?( [a-z]+)?$', re.U)

先來看一下這段敍述裡用到的正則表達式語句:

  • 錨點 ^$ :^表示匹配字串的開頭,$表示匹配字串的結尾

  • 分組和捕獲():()會創造一個捕獲性分組,之後可以用re_userdict.match(line).groups()來得到己匹配的分組

  • lazy匹配,.+?.+會貪心地(盡可能多地)匹配,加上?之後變成lazy匹配。如果實際去測試移除?之後的效果,會發現字串全部被分到第一個組別。

  • 或運算符[]:如[0-9]匹配09中的其中一個字

  • 數量符?:匹配零次或一次?前面的東西

  • re.compile(pattern, flags=0)
    來自re.compile文檔

    Compile a regular expression pattern into a regular expression object,
    which can be used for matching using its match(), search() and other
    methods, described below. The expression’s behaviour can be modified
    by specifying a flags value.

    re.compile會將pattern編譯成正則表達式物件。我們之後就可以用它來matchsearch其它串。

  • re.U:
    根據Python - Regular Expressions

    Interprets letters according to the Unicode character set. This flag affects the behavior of \w, \W, \b, \B.

    Python2 - re.U

    Make the \w, \W, \b, \B, \d, \D, \s and \S sequences dependent on the Unicode character properties database. Also enables non-ASCII matching for IGNORECASE.

    re.U會根據Unicode字集來解釋字元。

注:Unicode與UTF-8的差異可以參考What’s the difference between Unicode and UTF-8? [duplicate]

回頭看一下這段敍述:

re_userdict = re.compile('^(.+?)( [0-9]+)?( [a-z]+)?$', re.U)

我們現在可以了解到,它會匹配一個字串,並將它分成三組。
第一組是配對一至多個任意字元,直到空白出現為止。
第二組是配對空白加上一至多個數字。
第三組是配對空白加上一至多個英文字母。

測試一下re_userdict的效果:

re_userdict = re.compile('^(.+?)( [0-9]+)?( [a-z]+)?$', re.U)
re_userdict.match('AT&T 3 nz').groups()
Out[7]: ('AT&T', ' 3', ' nz')

順便測試一下如果第一組使用貪婪匹配會怎麼樣?

re_userdict_greedy = re.compile('^(.+)( [0-9]+)?( [a-z]+)?$', re.U)
re_userdict_greedy.match('AT&T 3 nz').groups()
Out[4]: ('AT&T 3 nz', None, None)

可以發現,字串全部被分到第一個組別,因為.+的涵義本來就是匹配任意字元。

re_eng

# re.U: Unicode matching
re_eng = re.compile('[a-zA-Z0-9]', re.U)

re_eng對應到單個英文或數字。

re_han

re_han_defaultre_han_cut_all會分別在不同分詞模式下被調用。

# \u4E00-\u9FD5a-zA-Z0-9+#&\._ : All non-space characters. Will be handled with re_han
# re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._%]+)", re.U)
# Adding "-" symbol in re_han_default
re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._%\-]+)", re.U)
re_han_cut_all = re.compile("([\u4E00-\u9FD5]+)", re.U)

先來看一下這段敍述裡用到的正則表達式語句:

  • \u4E00表示的是這個字,\u9FD5表示的是[\u4E00-\u9FD5]表示所有漢字
  • []外,.要用\來跳脫:
    dot = re.compile('.+') #這裡的.是任意字元的意思
    dot.match('abc')
    Out[60]: <_sre.SRE_Match object; span=(0, 3), match='abc'>
    
    dot = re.compile('\.+') #這裡的\.代表小數點本身
    dot.match('abc') #輸出為None
    
    dot.match('...')
    Out[82]: <_sre.SRE_Match object; span=(0, 3), match='...'>
    
    []內,.失去它原有的特殊涵義:
    dot = re.compile('[.]+') #match小數點本身
    dot.match('.')
    Out[71]: <_sre.SRE_Match object; span=(0, 1), match='.'>
    dot.match('abc') #輸出None
    #現在[.]只能match到小數點本身
    
  • []外,-不需使用\來跳脫:
    #在[]外,'-'代表減號本身
    dash = re.compile('-+')
    dash.match('--')
    Out[66]: <_sre.SRE_Match object; span=(0, 2), match='--'>
    
    []內,-有特殊涵義,所以要用\來跳脫:
    dot = re.compile('[a\-z]+', re.U)
    dot.match('a-z')
    Out[76]: <_sre.SRE_Match object; span=(0, 3), match='a-z'>
    
    dot = re.compile('[a-z]+', re.U)
    dot.match('a-z')
    Out[80]: <_sre.SRE_Match object; span=(0, 1), match='a'> 
    #這時的-有特殊涵義,所以無法匹配減號,輸出為None
    
  • 參考以下實驗:在[]內,所有特殊字元都會失去他們應有的意義。
    mis = re.compile('[+#&._%-]+')
    mis.match('-#+.&-%_abc')
    #因為以上字元在[]中己無特殊涵義,所以可以被配對
    Out[101]: <_sre.SRE_Match object; span=(0, 8), match='-#+.&-%_'>
    mis.match('abc') #輸出None,可以說明此處的.是與小數點而非任意字元配對
    
    具體可以參考re.compile文檔

回頭看看這兩句正則表達式:

re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._%\-]+)", re.U)
re_han_cut_all = re.compile("([\u4E00-\u9FD5]+)", re.U)

re_han_cut_all的作用是與一個或多個漢字配對。
re_han_default的作用是與一個或多個漢字,英數字,+#&._%-等字元配對。

re_skip

# \r\n|\s : whitespace characters. Will not be handled.
re_skip_default = re.compile("(\r\n|\s)", re.U)
re_skip_cut_all = re.compile("[^a-zA-Z0-9+#\n]", re.U)

測試:

re_skip_default.split("  a  bcd\r\nefg")
#用空白及換行符來分割字串
Out[108]: ['', ' ', '', ' ', 'a', ' ', '', ' ', 'bcd', '\r\n', 'efg']

re_skip_cut_all.split("aefawef wefawef 3242rf #$()#U) ")
#將能配對的及無法配對的分開
Out[110]: ['aefawef', 'wefawef', '3242rf', '#', '', '', '#U', '', '']

jieba/finalseg/init.py

re_han

#一個或多個漢字
re_han = re.compile("([\u4E00-\u9FD5]+)")

測試re_han

>>> s = "自然語言處理"
>>> re_han.match(s)
<_sre.SRE_Match object; span=(0, 6), match='自然語言處理'>
>>> re_han.match(s).group()
'自然語言處理'
>>> re_han.match(s).group(1)
'自然語言處理'
>>> re_han.match(s).group(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group

注:要抓取re配對到的group,索引應該從group(1)開始,參考Why regular expression’s “non-capturing” group is not working

re_skip

#[a-zA-Z0-9]+ : 一個或多個英數字
#\.\d+ : ".加上一個或多個數字"
#?: : 表示該group會被配對,但無法被抓取
#(?:\.\d+)? : 配對該group零次或一次
#%? : 配對%零次或一次
re_skip = re.compile("([a-zA-Z0-9]+(?:\.\d+)?%?)")

測試re_skip

>>> re_skip.match("asw42d.34243%").group(1)
'asw42d.34243%'
>>> re_skip.match("asw42d.34243%").group(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group

為了測試?:的效果,把它拿掉看看:

re_skip2 = re.compile("([a-zA-Z0-9]+(\.\d+)?%?)")
>>> re_skip2.match("asw42d.34243%").group(1)
'asw42d.34243%'
>>> re_skip2.match("asw42d.34243%").group(2)
'.34243'
>>> re_skip2.match("asw42d.34243%").group(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group

如果在\.\d+前沒加上?:,那麼我們在用.group時就會看到(\.\d+)這個組中組,而這並不是我們所希望見到的結果。

jieba/posseg/init.py

#一個或多個漢字
re_han_detail = re.compile("([\u4E00-\u9FD5]+)") 
#一個或多個 (.跟0-9)或英數字
re_skip_detail = re.compile("([\.0-9]+|[a-zA-Z0-9]+)") 
#一個或多個漢字或英數字或+#&._
re_han_internal = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._]+)") 
#\s等於[ \t\n\r\f\v]:https://docs.python.org/3/library/re.html#re.compile
re_skip_internal = re.compile("(\r\n|\s)") 
#一個或多個英數字
re_eng = re.compile("[a-zA-Z0-9]+")
#一個或多個小數點或數字 
re_num = re.compile("[\.0-9]+") 
#長度為1的英數字
re_eng1 = re.compile('^[a-zA-Z0-9]$', re.U) 

參考連結

regex101
Python - Regular Expressions
正则表达式的功法大全,做NLP再也不怕搞不定字符串了
re.compile文檔
Python2 - re.U
What’s the difference between Unicode and UTF-8? [duplicate]
Why regular expression’s “non-capturing” group is not working
What is a non-capturing group? What does (?: ) do?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Cocos Creator是一个功能强大的游戏开发引擎,它允许开发者创建各种类型的游戏,包括街霸游戏。但是,Cocos Creator本身并不提供街霸游戏的源码,开发者需要自己设计和编写游戏的逻辑、界面以及各种游戏元素。 要创建一个街霸游戏,首先需要进行角色的设计和动画制作。可以使用Cocos Creator内置的动画编辑器来创建和编辑角色的动画效果。然后,开发者需要设计游戏场景,包括背景、道路、建筑等等,可以使用Cocos Creator提供的场景编辑器进行创建和布置。 在街霸游戏中,角色之间的战斗是重要的内容。为了实现战斗机制,需要编写适当的代码来实现攻击、防御、技能等动作的触发和效果。开发者可以使用Cocos Creator的脚本编辑器来编写游戏逻辑脚本,实现战斗机制,并确保游戏的平衡性和可玩性。 此外,街霸游戏还可能包括多人对战模式,可以使用Cocos Creator的网络模块来实现多人对战功能。开发者还可以使用音效编辑工具来添加游戏音效,以提升游戏的体验和乐趣。 总体来说,创建一款街霸游戏需要进行多个方面的设计和开发工作,包括角色设计、动画制作、场景布置、战斗机制实现、网络功能等等。Cocos Creator作为游戏开发引擎,为开发者提供了一系列强大的功能和工具,可以帮助开发者创建出高质量、富有创意的街霸游戏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值