劉亦菲生日當天,引發了我對正則的思考

前兩天從網上採集到一條短影片數據(刷短影片),發現六公主連排5部劉亦菲主演的電影!甚是震驚,太有牌面了,看了一下日子是8月25號,嗷,原來當天是劉亦菲的生日。巧了,正好也是我家柴犬旺財的3歲生日😀。

言歸正傳,我們看到這條數據的

標題:#劉亦菲35歲生日獲央視獨寵# 神仙姐姐生日快樂!

為了分析數據,我們需要獲取數據中所提到的話題#劉亦菲35歲生日獲央視獨寵#。提問:你能想到幾種實現方式呢?歡迎評論區留言。

正則

想必細心看標題的朋友一定會猜到本文的主人公——正則,這玩意優點是寫起來快,但缺點也顯而易見,性能差,跟批處理一個德性,反人類的難記,長時間不用,每次都要重新學習,日常開發中也就偶爾寫工具用一下。因此,我們只需要把常用的正則理解透就可以了,不要有心理包袱,看完本文,大部分的正則使用場景就可以活學活用。

場景一:匹配兩個#之間的字元串

現在我們把需求轉換成實現思路,想要獲取上述數據中的話題,其實就是匹配兩個#之間的文本內容,show code:

正則表達式:#.*?#

    public static void main(String[] args) {
        String title = "#劉亦菲35歲生日獲央視獨寵# 神仙姐姐生日快樂!";
        Pattern p = Pattern.compile("(#.*?#)");
        Matcher m = p.matcher(title);
        while (m.find()) {
            String group = m.group(0);
            System.out.println("話題:" + group);
        }
    }

輸出

話題:#劉亦菲35歲生日獲央視獨寵#

可以看出,輸出的結果就是該條數據的話題,給大家分析一下這個正則表達式:
.表示任意字元,?平時表示匹配0個或者多個,此時它表示不貪婪,那什麼是貪婪呢?

說到貪婪,那就不得不提這兩個限定符:* 和 + ,它們兩個就是貪婪的!*表示匹配0個或者多個,+表示匹配至少一個,可以發現它們的共性:都會儘可能匹配更多。但,只要緊跟在它們的後面加上一個 ? ,讓他們捫心自問,就可以實現非貪婪,即最小匹配。比如,我們在標題上再加一個#,此時標題變為#劉亦菲35歲生日獲央視獨寵## 神仙姐姐生日快樂!,然後正則表達式我們不使用?,此時,運行程式碼後輸出結果為:

話題:#劉亦菲35歲生日獲央視獨寵##

比較兩次的執行結果,我們會發現輸出結果多了一#,這樣就很貪婪,違背了我們的初衷,這就是?的妙用。

場景二:只匹配#開頭的字元串

我們知道不同平台的話題格式是不一樣的,比如抖音,它的話題形式就與微博不同,只是#開頭,並沒有成對的#與其呼應:

標題:#劉亦菲35歲生日獲央視獨寵 神仙姐姐生日快樂!

如果是這種形式的話題,愛動腦筋的朋友就會發現無非就是將後面的#變為一個空格或者多個空格唄。沒錯,我們可以使用

正則表達式:#.*?\\s+

    public static void main(String[] args) {
        String title = "#劉亦菲35歲生日獲央視獨寵  神仙姐姐生日快樂!";
        Pattern p = Pattern.compile("(#.*?\\s+)");
        Matcher m = p.matcher(title);
        while (m.find()) {
            String group = m.group(0);
            System.out.println("話題:" + group);
        }
    }

輸出

話題:#劉亦菲35歲生日獲央視獨寵 

Tips:\s 表示匹配所有空白符,它不僅僅可以匹配空格,還可以匹配換行等空白字元,如果再加上+,組合起來就表示甭管幾個空白字元,統統all in。

不過,不要高興得太早,心細的小明還發現話題可能會存在文末的情況:

標題:神仙姐姐生日快樂!#劉亦菲35歲生日獲央視獨寵

此時是沒有空白字元結束的,上面的正則就不滿足這種情況。那如何是好?小明當時就想到了兩個方案:

  • 方案一:不管話題在不在文末,我們在匹配之前全部給標題追加一個空白字元,這樣就可以人為干預,巧妙地避開了這種情況,曲線救國,便於正則匹配
  • 方案二:使用正則表達式#.*?$

Tips:方案二中的$ 是用來匹配輸入字元串的結尾位置,組合起來就表示匹配#開頭,一直到句末的話題。

進階

前面兩種場景我們雖然獲取到了兩個#之間或者#開頭的內容,實現了需求,但是匹配的結果還是會帶上#,貪婪的小明不滿足現狀(產品強制要求),可不可以不帶呢?
當然可以了!小明又想到了兩個方案:

  • 方案一:得到匹配的話題後,再二次文本處理去掉#
  • 方案二:使用正則表達式?<=和?=一步到位,直接去除#

使用方法

  • (?<=exp2)exp1:表示匹配 exp2 後面的 exp1
  • exp1(?=exp2):表示匹配 exp2 前面的 exp1
    那結合以上我們實際使用的所有場景,最終這個理想的正則表達式就正式出爐(好熱):((?<=#).*?(?=#|\s+))
    (好热)
    反應慢的朋友,可以慢點理解,我們先運行試一下(不自信):
    public static void main(String[] args) {
        String title = "#劉亦菲# #神仙姐姐 #生日快樂# #劉亦菲35歲生日獲央視獨寵";
        Pattern p = Pattern.compile("((?<=#).*?(?=#|\\s+|$))");
        Matcher m = p.matcher(title);
        while (m.find()) {
            String group = m.group(0);
            System.out.println("話題:" + group);
        }
    }

輸出

話題:劉亦菲
話題:
話題:神仙姐姐
話題:生日快樂
話題:
話題:劉亦菲35歲生日獲央視獨寵

Tips:正則表達式中的|表示或,即多項之間的一個選擇,就像今天你看完本文,感覺不錯的話,
必須做出(關注|點贊|星標)其中的一個選擇!
從程式碼的輸出結果我們可以看出,目前的正則是經得住實際考驗的。但是唯一美中不足的是,會有空字元串的情況出現(產品不同意)。
這該如何是好?
目前小明的解決方案是,拿到匹配結果時,過濾掉空字元串。能解決問題的辦法就是好辦法。不過,肯定有更完美的表達式可以實現這個需求,此處拋磚引玉,期待你在評論區分享!

常用的正則表達式分享

  • 微訊號(並不能幫你要到男神|女神的微訊號)
^[a-zA-Z][a-zA-Z\d_-]{5,19}$

未完待續……