iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
自我挑戰組

初級紅寶石魔法師心得分享。系列 第 19

D-11, Ruby 正規表達式(三) 字符 && Regular Expression Matching

經過兩天,開始看得懂/a{3,}?/i是什麼意思了。

這是一個比較歪樓的比喻。

這是一段隨意從網路上抓下來的Regexp
寫得很嚴謹,e-mail網址的狀況都有設想到。

/^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})$/i

但在某些狀況下與這段是一樣的意思。

/^.+@.+$/

例如:

2.7.3 :020 > reg = /^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})$/i

2.7.3 :022 > mail = "A123__DD.dsf@gmail.com.taipei"
 => "A123__DD.dsf@gmail.com.taipei"

2.7.3 :023 > mail.match?(reg)
 => true
2.7.3 :024 > reg = /^.+@.+$/
 => /^.+@.+$/
2.7.3 :025 > mail = "A123__DD.dsf@gmail.com.taipei"
 => "A123__DD.dsf@gmail.com.taipei"
2.7.3 :026 > mail.match?(reg)
 => true

/^.+@.+$/這樣也過了!?先不論夠不夠嚴謹(因為如果我這樣寫連@@@這樣也能過),重點在於再嚴謹的表達式也要搭配正確的方法使用,如果沒有正確的方法,資料庫內也是只會出現一堆奇奇怪怪的mail,當然email的涵蓋範圍很大,各種形式的mail都有可能出現,去驗證e-mail到底正不正確,不如花時間去查這mail註冊過沒或是否uniq,或是假設mail不正確時,怎麼讓註冊不通過或讓用戶修改。

正規表達式用在檢測資料內有無特定資料,會比去規定用戶輸入正不正確時輕鬆,例如密碼要大小寫英文,不可以11111123123這樣。

結論是Regexp不適合驗證email??!!


天書時間

/./:點代表任何字元包含符號,但只有m模式下可匹配\n

/abc/: 這樣等於要匹配有"abc"的部分。

/[abc]/: 這樣等於a or b or c。另外特殊字元被[]包住失去效果,成為單純符號。

2.7.3 :055 > /[$%]/.match("ac%$%")
 => #<MatchData "%">
2.7.3 :056 > "ac%$%".scan(/[$%]/)
 => ["%", "$", "%"]

/[^abc]/:這樣等於不要a or b or c

2.7.3 :029 > "applebc".scan(/[^abc]/)
 => ["p", "p", "l", "e"]

/[a-z]//[A-Z]/: Any single character in the range。
/[0-9]/: Any single number in the range。
突然不會講中文...

\w:英文、數字與底線。
\W:非英文、數字與底線。

2.7.3 :068 > /\w*/.match("apple")
 => #<MatchData "apple">
2.7.3 :069 > /\w/.match("apple")
 => #<MatchData "a">

\d:數字。
\D:非數字。

\s:空白字元,包括空白、定位、換行、換頁。 它們 ====> ( \t\r\n\f\v)
\S:非空白字元。

2.7.3 :094 > "You are a\ngood boy".scan(/(\s)/)
 => [[" "], [" "], ["\n"], [" "]]
2.7.3 :095 > "You are a\ngood boy".scan(/(\S)/)
 => [["Y"], ["o"], ["u"], ["a"], ["r"], ["e"], ["a"], ["g"], ["o"], ["o"], ["d"], ["b"], ["o"], ["y"]]

錨(補充)

(?=regexp):確保後面的資料符合regexp。

2.7.3 :109 > /\w*+@(?=(gmail.com))/.match?("apple@hotmail.com")
 => false
2.7.3 :110 > /\w*+@(?=(gmail.com))/.match?("apple@gmail.com")
 => true

(?!regexp):確保後面的資料符合regexp。

2.7.3 :111 > /\w*+@(?!(gmail.com))/.match?("apple@gmail.com")
 => false

(?<=regexp):確保前面的資料符合regexp。

2.7.3 :120 > /\d*(?<=0978)/.match("0978123111")
 => #<MatchData "0978">
2.7.3 :121 > /\d*(?<=0978)/.match?("0978123111")
 => true
2.7.3 :122 > /\d*(?<=0978)/.match?("0968123111")
 => false

(?<!regexp):確保前面的資料符合regexp。


實作看看吧

請多愛用令人尊敬且有愛的大神製作的Rubular:https://rubular.com/

def taiwan_mobile_number(nums)
  reg = /\A((\+8869\d{2})|(09\d{2}))+((\d{6})|(\-+\d{3}){2}|(\s+\d{3}){2})\z/
  nums.match?(reg)
end

puts taiwan_mobile_number("0954 666 666") #=> true
puts taiwan_mobile_number("0905-999-999") #=> true
puts taiwan_mobile_number("0905999888") #=> true
puts taiwan_mobile_number("+886913-333-333") #=> true
puts taiwan_mobile_number("+886913555666") #=> true

這題其實非常簡單,利用已經有的確定實例,來拼湊出Regexp
我把+8869..09..為第一區塊,也可以把+8860就當第一區塊。
-...-...\s...\s...當第二區塊,因為有-後面就不該有\s,所以直接都重複兩次。
當然一定有更漂亮寫法,我只是依照資料以及需求來寫。

回頭認識一開始那段e-mail的測試Regexp

先不管其中是不是有更好的寫法。

/^(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})$/i

第一個區塊。

(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*

[A-Za-Z0_9]+_+ : 大小寫英文與數字組合,加1或多個__沒有特殊意思,不需反斜線。
[A-Za-z0-9]+-+ : 大小寫英文與數字組合,加1或多個--需要反斜線。
[A-Za-z0-9]+.+ : 大小寫英文與數字組合,指定加1或多個.\.代表要真正的.
[A-Za-z0-9]+++ : 大小寫英文與數字組合,加1或多個+
這四個可能的組合會出現0或多次,所以有一個*()外。

第二區塊。

[A-Za-z0-9]+ #大小寫英文與數字組合出現1或多次。

這個蠻重要的@前面不可以有._@這些符號。

第三區塊@,代表一定會有一個@

第四區塊。@後面。

((\w+\-+)|(\w+\.))*

(\w+-+) : 任意非符號包含_字元加1或多個-
(\w+.) : 任意非符號包含_,可以加真正的.
以上組合可以出現0或多次所以()*

第五區塊。

\w{1,63}\.[a-zA-Z]{2,6}

任意字元最少1個最多63個,一定有一個.,接2~6個大小寫英文字母。

雖然這邊似乎怪怪的,e-mail有可能會有複數/(\.[a-zA-Z])*/,不過沒關係,目的在認識別人寫的Regexp

雖然我把正確表達式用三天分享很混,希望有機會能讓自己再次遇到Regexp不會再那麼陌生。


今天的leetcode10. Regular Expression Matching
題目連結:https://leetcode.com/problems/regular-expression-matching/
題目重點:p是一個實體,將實體變成Regexp,記得用#{}

# @param {String} s
# @param {String} p
# @return {Boolean}
def is_match(s, p)
  reg = /(#{p})/
  s.match(reg)
  s == $1
  # s.match(/(#{p})/)
  # s == $1
end
2.7.3 :039 > p = "a"
 => "a"
2.7.3 :040 > reg = /(#{p})/
 => /(a)/
2.7.3 :041 > s = "aa"
 => "aa"
2.7.3 :042 > s.match(reg)
 => #<MatchData "a" 1:"a">
2.7.3 :043 > s == $1
 => false

非常Easy的一題對吧,但這題其實被leetcode分類為Hard喔。

當熟悉表達式這些天書符號後,可以再多看看Regexp裡面的一些方法,對於leetcode的一些對字串檢查或替換的問題,也是比上面花不了多幾個字就解決了。


上一篇
D-12, Ruby 正規表達式(二) 量詞 、錨 && Reverse Vowels of a String
下一篇
D-10.Rails N+1 queries and kill N+1
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言