iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
自我挑戰組

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

D-13, Ruby 正規表達式(一) Regexp && Valid Palindrome

剛開始看不懂那些亂碼時,真的很痛苦。

Regular Expression常簡寫regexp,也是Ruby內的一個Class

2.7.3 :001 > Regexp.is_a? Class
 => true

Why need Regexp?

資料檢索非常好用,查找或替換資料內特定內容,規範輸入內容格式,這三點為常見用途。

2.7.3 :025 > ("A".."Z").to_a.join.scan(/[AHY]/)
 => ["A", "H", "Y"]
#掃描"A","H","Y"這三個字元,如果沒有[],等於是要找"AHY"這段。

2.7.3 :034 > "hello".gsub(/[aeiou]/, "ahy")
 => "hahyllahy"
#把這五個母音,替換成"ahy"。

2.7.3 :248 > "abc".match(/./)
 => #<MatchData "a">
2.7.3 :249 > "abc".match(/../)
 => #<MatchData "ab">
2.7.3 :250 > "abc".match(/.../)
 => #<MatchData "abc">
#查找符合資料。

.是代表任何一個字。幾個.幾個字,所以一段最簡單的表達式/./就完成了。


先來認識Regexp.Class

Ruby中Regexp中通常由/ /包裝。

2.7.3 :004 > /ASH/.class
 => Regexp
2.7.3 :007 > %r{123}.class
 => Regexp
2.7.3 :011 > %r{123}
 => /123/

%r才是重點,後面要用{}[]()都可以形成regexp
所以基本上看到/ /%r包住的code時,就要微笑著仔細解讀一下了。
字串可以直接成為Regexp物件,一樣是new出來,但是這部分我們先跳過。


認識WTF的四個主要修飾符(選項:optional)

i:ignorecaes,忽略大小寫。

2.7.3 :046 > "abcdefghijklmnopqrstuvwxyz".scan(/[WTF]/)
 => []
2.7.3 :047 > "abcdefghijklmnopqrstuvwxyz".scan(/[WTF]/i)
 => ["f", "t", "w"]

m:multiline。

2.7.3 :237"> "a#123
2.7.3 :238"> opi[b".match(/a.*b/)
 => nil
2.7.3 :239"> "a#123
2.7.3 :240"> opi[b".match(/a.*b/m)
 => #<MatchData "a#123\nopi[b">

這邊看不懂/a.*b/先不要理會,m直接點說明是允不允許檢測資料內有沒有換行\n
但應該要理解為,匹配多行,把換行字符\n識別為正常字符。

2.7.3 :252"> "abc
2.7.3 :253"> def"
 => "abc\ndef"

比較特別的是.可以在m模式下匹配換行。

2.7.3 :285"> "a
2.7.3 :286"> b".match(/.../)
 => nil
2.7.3 :287"> "a
2.7.3 :288"> b".match(/.../m)
 => #<MatchData "a\nb">

x:extended。

2.7.3 :280 > "abc".match(/. . ./)
 => nil
2.7.3 :281 > "abc".match(/. . ./x)
 => #<MatchData "abc">

忽略掉空格,註解也可以忽略。

2.7.3 :289/> "abc".match(/
2.7.3 :290/>   . #無意義的註釋
2.7.3 :291/>   . #繼續無意義
2.7.3 :292/>   . #為了示範的註釋
2.7.3 :293/>   /x)
 => #<MatchData "abc">
2.7.3 :294/> "abc".match(/
2.7.3 :295/>   . #無意義的註釋
2.7.3 :296/>   . #繼續無意義
2.7.3 :297/>   . #為了示範的註釋
2.7.3 :298/>   /)
 => nil

x使用上還有一些細節,這邊先認識這樣。

o這是大概最燒腦的部分,這邊只有簡單解釋,想詳細暸解請前往下方引文原網址。

the go-to source for regular expressions:https://www.regular-expressions.info/ruby.html
/o causes any #{…} substitutions in a particular regex literal to be performed just once, the first time it is evaluated. Otherwise, the substitutions will be performed every time the literal generates a Regexp object.
最簡單的說法是,有o,編譯#{}只執行一次保持固定,沒有o會重複執行。
表達式在能匹配前,會有兩個步驟,確認/abc/abc(字串),再去編譯/abc/。編譯完成後就會進行緩存。

2.7.3 :305 > /abc/.object_id
 => 1100
2.7.3 :306 > /abc/.object_id
 => 1120
2.7.3 :307 > /abc/.object_id
 => 1140

而在我們需要跑循環時,如果是元字串(source)是不變的,為了避免重新編譯緩存(為了增加效率),表達式不會去重新編譯,只會記住第一次的結果

2.7.3 :308 > ["a", "b", "c"].map{|x| /x/.object_id}
 => [1160, 1160, 1160]

但如果使用了#{}

2.7.3 :310 > var = "abc"
 => "abc"
2.7.3 :312 > ["a", "b", "c"].map{|x| /#{x}/.object_id}
 => [1200, 1220, 1240] #會重新編譯所以位置不同。
2.7.3 :313 > ["a", "b", "c"].map{|x| /#{x}/o.object_id}
 => [1260, 1260, 1260] #有o之後就不會進行重新編譯。

上面部分只是示範特性。
o必須符合狀況使用,最簡單的就是如果元字串會改變,就不適合使用。

def test_regexp(string)
  regexp = [".....", "....", "...", "..", "."][rand(5)]
  #方法內,這行會重複執行。
  string.map {|str| str.match(/#{regexp}/)}
end

2.7.3 :370 > test_regexp(["abc", "dfg", "asd"])
 => [#<MatchData "abc">, #<MatchData "dfg">, #<MatchData "asd">]
2.7.3 :371 > test_regexp(["abc", "dfg", "asd"])
 => [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :372 > test_regexp(["abc", "dfg", "asd"])
 => [nil, nil, nil] #可能就是rand到`.....`或`....`。

可以看到我們表達式,假設是希望[".....", "....", "...", "..", "."]隨機選一個,所以三次執行結果都不同。

使用o

def test_regexp(string)
  regexp = [".....", "....", "...", "..", "."][rand(5)]
  #方法內,這行會重複執行。
  string.map {|str| str.match(/#{regexp}/o)}
end

2.7.3 :006 > test_regexp(["abc", "dfg", "asd"])
 => [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :007 > test_regexp(["abc", "dfg", "asd"])
 => [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :008 > test_regexp(["abc", "dfg", "asd"])
 => [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]
2.7.3 :009 > test_regexp(["abc", "dfg", "asd"])
 => [#<MatchData "a">, #<MatchData "d">, #<MatchData "a">]

不管幾次,都只記住第一次編譯出來的/./,那這就不是我們要的了。


new的差異。

2.7.3 :018 > Regexp.new("abc")
 => /abc/
2.7.3 :019 > Regexp.new("abc", Regexp::IGNORECASE)
 => /abc/i
2.7.3 :020 > Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE | Regexp::EXTENDED)
 => /abc/mix

new建構表達式,感覺就像使用method一樣,帶入參數,建構出你要的正規表達式。

2.7.3 :023 > string = "abc"
 => "abc"
2.7.3 :024 > Regexp.new(string)
 => /abc/
2.7.3 :026 > start = "abcd"
 => "abcd"
2.7.3 :027 > finish = "wxyz"
 => "wxyz"
2.7.3 :028 > Regexp.new(start + ".+" + finish + "\.")
 => /abcd.+wxyz./
2.7.3 :030 > /#{start}.+#{finish}\./
 => /abcd.+wxyz\./

直接以//建構,感覺上就等於直接在寫一個表達式,但其實都可以,實際上兩個寫法看自己喜歡。反而//寫法還有一個好處。

2.7.3 :050 > /.../i
 => /.../i
2.7.3 :053 > Regexp.new(/abc/, i)
Traceback (most recent call last):
(irb):53:in <main>': undefined local variable or method `i' for main:Object (NameError)
2.7.3 :003 > Regexp.new("abc", Regexp::IGNORECASE)
 => /abc/i

要帶選項,直接/.../i還比較方便。可是當不想被重構,或是你今天是要讓使用者輸入的值變成正規表達式,還是用new比較好。

2.7.3 :055 > one = /abc/
 => /abc/
2.7.3 :056 > two = /#{one}/
 => /(?-mix:abc)/
2.7.3 :057 > three = Regexp.new(one)
 => /abc/
2.7.3 :058 > one == two
 => false
2.7.3 :059 > one == three
 => true

(?-mix)是代表剛剛的介紹過三個選項i、m、x暫時關閉,下篇會提到,當然我們要檢測的真的就是/abc/那結果論沒差,但實際上兩者還是不同。寫在//的會進行轉譯,寫在Regexp.new( )裡的不會進行轉譯,依照你給的參數進行組合。

明天會開始介紹,那些令人看不懂的符號,但如果理解今天的部分,會發現明天說的非常簡單,就是要死命記憶而已= =+。


今天的leetcode125. Valid Palindrome
題目連結: https://leetcode.com/problems/valid-palindrome/
題目重點: 經典正規表達式解題。

# @param {String} s
# @return {Boolean}
def is_palindrome(s)
  s.downcase.gsub(/[^0-9a-z]/, '') == s.downcase.gsub(/[^0-9a-z]/, '').reverse
end
2.7.3 :030 > s = "A man, a plan, a canal: Panama"
 => "A man, a plan, a canal: Panama"
2.7.3 :031 > s = s.downcase.gsub(/[^0-9a-z]/, '')
 => "amanaplanacanalpanama"
2.7.3 :032 > y = s.reverse
 => "amanaplanacanalpanama"
2.7.3 :033 > s == y
 => true

上一篇
D-14.Rspec 從零開始寫測試(四) 私有方法測不測? && Maximum Product of Three Numbers
下一篇
D-12, Ruby 正規表達式(二) 量詞 、錨 && Reverse Vowels of a String
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言