【NLP】Day 4: 什麼!文本也可以偷天換日?正規表達式 (2) 替換
你低著頭,他們就會知道你在說謊;即便你抬起頭,他們也會知道其實你根本不知道真相。如果只用四個字就能說明清楚,就別用七個字。身體不要搖來搖去,要用堅定的眼神看著對方的眼睛,但別一直盯著看。說話清楚,但別太讓人印象深刻,可以偶爾幽默一下,但是也不要過頭到讓他捧腹大笑。這麼一來,他就只會在當下對你有好感,而你一離開,他也會立刻忘了你這個人。而且看在老天的份上,無論你想做什麼,都別做,因為在任何情況下…
Rusty Ryan《瞞天過海》
昨天我們已經簡單地認識了什麼是正規表達式(Regular Expression),也瞭解了正規表達式的性質,更認識了正規表達式在Python的函式庫中最常見的功能之一,也就是搜尋功能。讓我們在這裡簡單複習一下,正規表達式可以運用單一個字串,藉以匹配所有符合某個句法規則的字串;而這些字串藉由各種不同函式,可以幫助我們針對文本進行驗證、萃取,以及今天所要介紹的替換功能。正規表達式的運用,加上Python各種不同函式的靈活搭配,是踏入自然語言處理的第一張門票,是一切資料處理的根基,因此掌握正規表達式的運用其實比你我想的還要再更重要一點點。
啊所以到底什麼才是替換功能?
讓我們把話題拉回正規表達式的替換功能。大家看到這裡可能還不清楚,所謂正規表達式的「替換」功能,實際上到底指的是什麼事情。就讓我們直接來看一個例子:
最近有所上同學問我説,她在搭建聊天機器人的時候,發現若是在iPhone按下系統推薦的字詞,會自動產生出一個空白字元" "
,而這會導致機器人在判讀字串時產生錯誤。因為同學做的是查詢英漢字典機器人,機器人需要完整正確的字串,才有辦法依照字串找出正確的中文翻譯,但這時卻多了一個空白字元,該怎麼處理才好呢?比如說今天輸入的是ocean
,但程式端所接收到的字串卻是ocean
。同學很苦惱。
其實對Python稍微有一點點研究的同學,第一個想到的函式大概是 .replace()
,程式如下:
1 | message = "ocean " |
但以上這段程式碼好像也不能有效解決這位同學的問題,因為仔細想想,假如說我們今天想要知道 One Piece 是什麼意思?若套用以上的程式碼,反而會變成 OnePiece,像這種中間有空格的名詞,在計算語言學中,我們稱為proper noun(專有名詞),另外,儘管其實是有些差別,但有些人也會一併稱其為 entity(實體)。在這時,我們發現.replace()
已經不管用了,這時該怎麼辦?這時我們就必須要想辦法「將字串尾的空白字元取代掉」。
在軟體工程中有個很重要的觀念,叫做Divide and Conquer,也就是將一個看似難以解決的問題分成很多個小問題,並且逐一擊破,最後即便是大問題也可以解決,大至軟體工程,小至正規表達式,都是一樣的道理。讓我們重新回來看這個問題,「將字串尾的空白字元取代掉」,其中第一個問題則是要找出「字串尾」。
在正規表達式中,若要表達指定字串的第一個字元,會用^
來表示,也就是說,你要告訴電腦你想要的字串格式,其中的第一個字是什麼的時候,就可以透過^
來表達;另外,同樣的,若要表達指定字串的最後一個字元,在正規表達式中則會以$
來表示。再來會碰到的第二個問題則是「空白字元」。昨天,我們學到了數字的表達方式,也就是透過\d
來代表數字。而空白字元在正規表示法中,則是以\s
。這麼一來,我們就可以匹配所有字串尾有空白字元的字了。
1 | \s$ |
欸?沒錯,就是這麼簡單。那麼接著就可以將這串正規表達式加入Python當中進行替換了。在正規表達式的函式庫中,將字串進行替換的函式為re.sub()
,其中第一個參數放正規表達式,第二個參數則是放你想替換成的字符,在我們的例子裡就是「無」,第三個參數則是放要進行替換的字串。這麼一來,程式就可以寫成:
1 | import re #記得要加入正規表達式函式庫 |
1 | n |
從結果我們可以發現幾件事:
一、最後一個字都不是空白字元,這代表空白字元已經成功地被我們的正規表達式進行替換了!
二、就算最後一個字不是空白字元,正規表達式沒有匹配,函式仍會回傳原本的字串!
三、One Piece中間的空白字元仍留著,這代表所有proper noun最後一個字都不會是空白字元!
進階的替換功能
前面我們提到了如何運用正規表達式將字串整理成我們理想中的形式,現在要來介紹一些正規表達式中比較進階的替換用法。先讓我們進入實例:
美國佬很喜歡跟全世界不一樣,無論是重量單位、長度單位,以及日期都跟其他國家的寫法不一樣,那今天我們的任務是要將美國日期寫法改成大家都習慣的形式。
1 | # 美國日期寫法為 mm/dd/yyyy |
一樣,我們先來想想該如何以正規表達式將美國日期格式進行匹配。這邊可以回去看第三篇文章,其中提到的逃脫字元\
,由於/
也是屬於正規表達式的一員,所以這時就要透過逃脫字元\
告訴電腦在這裡的/
不是正規表達式,而是真正的 /。
1 | \d{2}\/\d{2}\/\d{4} |
這時問題就來了,該如何透過替換功能,將一串日期字串轉換成其他國家較常用的日期格式呢?不知你是否還記得,昨天我們提到可以將括號()
作為群組(group),並透過re.search()
將想要的群組輸出,這次我們就要透過先前學過的群組概念來進行替換。首先,先將想要替換的字串加入群組:
1 | (\d{2})\/(\d{2})\/(\d{4}) |
圈好群組後,就可以透過re.match()
來進行替換:
1 | usa_date = "09/19/2022" |
1 | 19/09/22 |
其中\\
後面的數字就是群組的數字,就是這麼簡單!
好啦!那我們今天的旅程就到這邊先結束了,明天就要邁入正規表達式的最終章,也就是萃取!