正規表示式是處理文字的強大工具,Python 的 re 模組提供完整支援。本文從 Unicode 處理流程開始,逐步介紹正規表示式的基本語法、元字元、範圍和閉包等概念,並搭配程式碼範例說明如何在 Python 中使用 re 模組進行搜尋、匹配和提取。文章涵蓋了單詞邊界、字元集合、重複、分組、轉義等關鍵技巧,並以實際案例展示如何應用正規表示式解決日期格式轉換、母音提取、子音-母音序列分析等常見問題。此外,文章也結合了 NLTK 函式庫,示範如何處理語言資料,例如統計詞頻、建立索引等。讀者可以透過這些範例,快速掌握正規表示式的使用技巧,並應用於各種文書處理任務。

隨著全球化的發展,多語言文書處理變得越來越重要。未來,我們可以期待看到更多根據 Unicode 的文書處理技術的發展和應用。同時,隨著人工智慧和機器學習技術的進步,我們也可以期待看到更多自動化的多語言文書處理工具的出現。

Unicode 處理流程圖

  graph LR;
    A[讀取檔案] --> B{檢查編碼};
    B -->|正確|> C[使用codecs.open讀取];
    B -->|錯誤|> D[指定正確編碼];
    C --> E[處理Unicode文字];
    D --> C;
    E --> F[輸出結果];

圖表翻譯: 此圖示展示了處理Unicode文字的基本流程,包括檢查編碼、使用正確的編碼讀取檔案、處理Unicode文字以及輸出結果。

正規表示式在Python中的應用

在Python中使用正規表示式(Regular Expressions)需要匯入re函式庫。以下是一個簡單的例子:

import re

我們還需要一個單詞列表來進行搜尋;這裡我們再次使用Words Corpus(第2.4節)。我們將對其進行預處理,以移除任何專有名詞。

wordlist = [w for w in nltk.corpus.words.words('en') if w.islower()]

基本元字元的使用

讓我們使用正規表示式ed$來找出以ed結尾的單詞。我們將使用re.search(p, s)函式來檢查模式p是否能在字串s中的任何位置被找到。我們需要指定感興趣的字元,並使用美元符號($),它在正規表示式的上下文中具有特殊的行為,即匹配單詞的結尾:

[w for w in wordlist if re.search('ed$', w)]

輸出結果:

['abaissed', 'abandoned', 'abased', 'abashed', 'abatised', 'abed', 'aborted', ...]

內容解密:

  • re.search('ed$', w)用於搜尋以ed結尾的單詞。
  • $符號表示單詞的結尾。

.萬用字元可以匹配任何單一字元。假設我們在填字遊戲中需要一個八個字母的單詞,其中第三個字母是j,第六個字母是t。對於每個空白單元格,我們使用一個句點:

[w for w in wordlist if re.search('^..j..t..$', w)]

輸出結果:

['abjectly', 'adjuster', 'dejected', 'dejectly', 'injector', 'majestic', ...]

內容解密:

  • ^..j..t..$用於匹配一個八個字母的單詞,其中第三個字母是j,第六個字母是t
  • ^符號表示單詞的開頭,.匹配任何單一字元。

範圍和閉包

T9系統用於在行動電話上輸入文字(參見圖3-5)。兩個或多個使用相同按鍵序列輸入的單詞被稱為文字同源詞(textonyms)。例如,holegolf都是透過按下4653這個序列輸入的。使用相同的序列還能產生哪些其他單詞?這裡我們使用正規表示式^[ghi][mno][jlk][def]$

[w for w in wordlist if re.search('^[ghi][mno][jlk][def]$', w)]

輸出結果:

['gold', 'golf', 'hold', 'hole']

內容解密:

  • ^[ghi][mno][jlk][def]$用於匹配符合特定條件的四個字母的單詞。
  • []內的字元順序不重要,因此^[hig][nom][ljk][fed]$會匹配相同的單詞。

讓我們進一步探索+符號。注意它可以應用於個別字母或括號內的字母集合:

chat_words = sorted(set(w for w in nltk.corpus.nps_chat.words()))
[w for w in chat_words if re.search('^m+i+n+e+$', w)]

輸出結果:

['miiiiiiiiiiiiinnnnnnnnnnneeeeeeeeee', 'miiiiiinnnnnnnnnneeeeeeee', 'mine', 'mmmmmmmmiiiiiiiiinnnnnnnnneeeeeeee']

內容解密:

  • ^m+i+n+e+$用於匹配一個或多個連續的mine字元。
  • +表示前面的字元出現一次或多次。

現在讓我們將+替換為*,後者表示“前面的字元出現零次或多次”。正規表示式^m*i*n*e*$將匹配所有使用^m+i+n+e+$找到的單詞,但也包括某些字母未出現的單詞,例如meminmmmmmm

其他正規表示式範例

以下是一些更多使用正規表示式來查詢符合特定模式的標記的範例,說明瞭一些新符號的使用:\{}()|

wsj = sorted(set(nltk.corpus.treebank.words()))
[w for w in wsj if re.search('^[0-9]+\.[0-9]+$', w)]

輸出結果:

['0.0085', '0.05', '0.1', '0.16', '0.2', '0.25', '0.28', '0.3', '0.4', '0.5', ...]

內容解密:

  • ^[0-9]+\.[0-9]+$用於匹配包含小數點的數字。
  • [0-9]匹配任何數字,\.匹配小數點。
[w for w in wsj if re.search('^[A-Z]+\$$', w)]

輸出結果:

['C$', 'US$']

內容解密:

  • ^[A-Z]+\$$用於匹配以大寫字母開頭並以美元符號結尾的單詞。
  • \$$用於匹配美元符號,因為 $ 在正規表示式中有特殊含義,所以需要使用 \ 進行轉義。

進一步閱讀

欲瞭解更多關於正規表示式的資訊,請參閱Python官方檔案中的re模組。

練習題

  1. 使用正規表示式找出所有以"un"開頭的單詞。
  2. 使用正規表示式找出所有包含連續三個以上相同字母的單詞。

正規表示式基本語法

  graph LR;
    A[基本元字元] --> B[.] 表示任意字元;
    A --> C[^] 表示字串開始;
    A --> D[$] 表示字串結束;
    E[字元集合] --> F[[]] 用於定義字元集合;
    E --> G[^] 在[]內表示取反;
    H[重複] --> I[*] 表示零次或多次;
    H --> J[+] 表示一次或多次;
    H --> K[?] 表示零次或一次;
    L[分組] --> M[()] 用於分組;
    N[轉義] --> O[\] 用於轉義特殊字元;

圖表翻譯: 此圖表展示了正規表示式的基本語法,包括基本元字元、字元集合、重複和分組等概念。其中,.用於匹配任意字元,^$ 分別用於匹配字串的開始和結束。字元集合使用 [] 定義,可以包含多個字元或範圍。重複符號 *+? 分別用於指定前面的元素出現零次或多次、一次或多次、以及零次或一次。分組使用 () 實作,可以對多個元素進行統一處理。轉義符號 \ 用於轉義具有特殊含義的字元,使其被視為普通字元處理。這些基本語法構成了正規表示式的基礎,使其能夠靈活高效地處理各種文字匹配和提取任務。

正規表示式在文書處理中的應用

正規表示式(Regular Expressions)是一種強大的文書處理工具,能夠幫助我們在文字中搜尋、提取和修改特定的模式。在本章中,我們將介紹正規表示式的基本概念及其在 Python 中的應用。

正規表示式的基本概念

正規表示式是一種特殊的字串,用於描述一組字串的模式。它由普通字元和特殊字元(稱為元字元)組成。元字元具有特殊的意義,用於指定搜尋模式的結構。

常見的元字元

元字元意義
.萬用字元,匹配任何單一字元
^匹配字串的開頭
$匹配字串的結尾
[abc]匹配方括號中的任意一個字元
[A-Z0-9]匹配指定範圍內的一個字元
`eding
*匹配前面的元素零次或多次(Kleene Closure)
+匹配前面的元素一次或多次
?匹配前面的元素零次或一次(可選)
{n}匹配前面的元素恰好 n 次
{n,}匹配前面的元素至少 n 次
{,n}匹配前面的元素最多 n 次
{m,n}匹配前面的元素至少 m 次且最多 n 次

正規表示式的範例

假設我們要匹配以「wit」、「wet」、「wait」或「woot」開頭的字串,可以使用以下正規表示式:

import re

pattern = r'w(i|e|ai|oo)t'
words = ['wit', 'wet', 'wait', 'woot', 'other']

for word in words:
    if re.search(pattern, word):
        print(f'{word} matches the pattern')

內容解密:

上述程式碼使用了 re.search() 函式來搜尋符合正規表示式的字串。其中:

  • w 匹配字元 ‘w’
  • (i|e|ai|oo) 是一個群組,匹配 ‘i’、’e’、‘ai’ 或 ‘oo’ 其中之一
  • t 匹配字元 ’t’

這個正規表示式能夠正確匹配 ‘wit’、‘wet’、‘wait’ 和 ‘woot’ 等字串。

在 Python 中使用正規表示式

在 Python 中,我們可以使用 re 模組來處理正規表示式。為了避免反斜線(\)被解釋為轉義字元,我們通常使用原始字串(raw string),即在字串前加上 r 字首。

import re

# 使用原始字串
pattern = r'\band\b'
text = 'This is an example sentence.'

# 搜尋符合正規表示式的模式
if re.search(pattern, text):
    print('Found a match')

內容解密:

上述程式碼使用了 re.search() 函式來搜尋符合正規表示式的模式。其中:

  • \b 是單詞邊界,用於確保 ‘and’ 是一個完整的單詞
  • r'\band\b' 是原始字串,避免了反斜線被解釋為轉義字元

正規表示式的實用範例

提取單詞中的母音

import re

word = 'supercalifragilisticexpialidocious'
vowels = re.findall(r'[aeiou]', word)
print(vowels)
print(len(vowels))

內容解密:

上述程式碼使用了 re.findall() 函式來提取單詞中的所有母音。其中:

  • [aeiou] 是一個字元集,匹配任何一個母音字母
  • re.findall() 傳回所有符合模式的子字串列表

統計連續母音的頻率

import re
import nltk

# 載入 treebank 詞函式庫
wsj = sorted(set(nltk.corpus.treebank.words()))

# 提取連續母音序列
fd = nltk.FreqDist(vs for word in wsj for vs in re.findall(r'[aeiou]{2,}', word))

# 輸出頻率分佈
for item in fd.items():
    print(item)

內容解密:

上述程式碼使用了 re.findall() 函式來提取連續的母音序列,並使用 NLTK 的 FreqDist 類別來統計這些序列的頻率。其中:

  • [aeiou]{2,} 匹配兩個或以上的連續母音
  • nltk.FreqDist() 用於統計各個序列的出現頻率

將日期字串轉換為整數列表

假設我們有一個日期字串 ‘2009-12-31’,我們可以使用正規表示式將其轉換為整數列表 [2009, 12, 31]:

import re

date_str = '2009-12-31'
date_list = [int(n) for n in re.findall(r'\d+', date_str)]
print(date_list)

內容解密:

上述程式碼使用了 re.findall() 函式來提取日期字串中的數字部分。其中:

  • \d+ 匹配一個或多個數字字元
  • int(n) 將提取出的數字字串轉換為整數

其他範例

省略單詞中的母音

import re

def compress(word):
    regexp = r'^[AEIOUaeiou]+|[AEIOUaeiou]+$|[^AEIOUaeiou]'
    pieces = re.findall(regexp, word)
    return ''.join(pieces)

english_udhr = nltk.corpus.udhr.words('English-Latin1')
compressed_text = [compress(w) for w in english_udhr[:75]]
print(' '.join(compressed_text))

內容解密:

上述程式碼定義了一個 compress() 函式,用於省略單詞中的母音。其中:

  • 正規表示式 r'^[AEIOUaeiou]+|[AEIOUaeiou]+$|[^AEIOUaeiou]' 用於匹配單詞開頭的母音序列、結尾的母音序列以及所有的子音
  • re.findall() 用於提取符合模式的子字串
  • ''.join(pieces) 將提取出的子字串連線起來,形成省略母音後的單詞

統計子音-母音序列的頻率

import re
import nltk

rotokas_words = nltk.corpus.toolbox.words('rotokas.dic')
cvs = [cv for w in rotokas_words for cv in re.findall(r'[ptksvr][aeiou]', w)]
cfd = nltk.ConditionalFreqDist(cvs)
cfd.tabulate()

內容解密:

上述程式碼使用了 re.findall() 函式來提取 Rotokas 語言單詞中的子音-母音序列,並使用 NLTK 的 ConditionalFreqDist 類別來統計這些序列的頻率。其中:

  • [ptksvr][aeiou] 匹配一個子音後面跟著一個母音的序列
  • nltk.ConditionalFreqDist() 用於統計各個子音-母音序列的頻率分佈

建立子音-母音序列的索引

import re
import nltk

rotokas_words = nltk.corpus.toolbox.words('rotokas.dic')
cv_word_pairs = [(cv, w) for w in rotokas_words for cv in re.findall(r'[ptksvr][aeiou]', w)]
cv_index = nltk.Index(cv_word_pairs)

print(cv_index['su'])
print(cv_index['po'])

內容解密:

上述程式碼建立了一個子音-母音序列到單詞的索引。其中:

  • (cv, w) 是子音-母音序列與對應單詞的配對
  • nltk.Index() 用於建立索引,以便快速查詢包含特定子音-母音序列的單詞。