iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
自我挑戰組

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

D-12, Ruby 正規表達式(二) 量詞 、錨 && Reverse Vowels of a String

昨天的重點複習/./就是一個最簡單的正規表達式。

先認識一下match=~

match回傳匹配的內容。
=~回傳匹配的位置。

2.7.3 :195 > /abc/.match("hello abc here abc")
 => #<MatchData "abc">
2.7.3 :196 > /abc/.match?("hello abc here abc")
 => true
#把檢查對象內符合表達式的內容,回傳出來。

2.7.3 :197 > /abc/ =~ "hello abc here abc"
 => 6
2.7.3 :198 > /bca/ =~ "hello abc here abc"
 => nil
2.7.3 :199 > /./ =~ "hello abc here abc"
 => 0
2.7.3 :204 > "hello abc here abc"[6]
 => "a"
#回傳符合的位置的index,當然0才是第一個。如果檢查對象內有兩個,只回傳第一個。

分組()

當需要檢測或找出的資料有兩組的例子。

2.7.3 :006 > /(Rails).*(easy)/.match("Do you like Rails? It so easy!")
 => #<MatchData "Rails? It so easy" 1:"Rails" 2:"easy">

.*先暴雷,*代表有0或多個。

2.7.3 :007 > $1
 => "Rails"
2.7.3 :008 > $2
 => "easy"

可以看到用$可以呼叫出匹配到的資料,有n組就可以到$n


開始認識那些鬼畫符。

那些符號

+?.*^$()[]{ }|\
以上都代表有特殊功能,.是代表任何一個字前面示範已充足展示,如果當想表達這些符號出來,都是利用\來處理。

2.7.3 :009 > /(\?).*(easy)/.match("Do you like Rails? It so easy!")
 => #<MatchData "? It so easy" 1:"?" 2:"easy">
 
 2.7.3 :010 > /(?).*(easy)/.match("Do you like Rails? It so easy!")
Traceback (most recent call last): undefined group option: /(?).*(easy)/ (SyntaxError)

2.7.3 :011 > /..\?../.match("Do you like Rails? It so easy!")
 => #<MatchData "ls? I">

量詞,重複的次數。

*:0或多次。
+:1或多次。 兩兄弟,一起示範。

2.7.3 :013 > /.*\?/.match("?")
 => #<MatchData "?">

2.7.3 :014 > /.*\?/.match("Date?")
 => #<MatchData "Date?">

2.7.3 :016 > /.+\?/.match("Date?")
 => #<MatchData "Date?">
 
2.7.3 :017 > /.+\?/.match("?")
 => nil
#如果?號前面沒有字元出現過至少一個就抓不到了。

{n}:指定n次。

2.7.3 :047 > /.*/.match("hello world")
 => #<MatchData "hello world">
 
2.7.3 :048 > /.{5}/.match("hello world")
 => #<MatchData "hello">
 
2.7.3 :049 > /.{3}/.match("hello world")
 => #<MatchData "hel">

{n,}:n次或更多。

2.7.3 :053 > /a{3,}/.match("a")
 => nil
 
2.7.3 :054 > /a{3,}/.match("aaa")
 => #<MatchData "aaa">
 
2.7.3 :055 > /a{3,}/.match("aaaaaaaaa")
 => #<MatchData "aaaaaaaaa">

{,m}:m次或更少次。

2.7.3 :056 > /a{,3}/.match("aaaaaaaaa")
 => #<MatchData "aaa">
 
2.7.3 :057 > /a{,3}/.match("aa")
 => #<MatchData "aa">
 
2.7.3 :058 > /a{,3}/.match("a")
 => #<MatchData "a">

{n,m}:最少n次,最多m次。

2.7.3 :059 > /a{2,3}/.match("a")
 => nil
 
2.7.3 :060 > /a{2,3}/.match("aa")
 => #<MatchData "aa">
 
2.7.3 :061 > /a{2,3}/.match("aaaaa")
 => #<MatchData "aaa">

?:本身為0或1次,常與其他語法搭配。簡單一點理解,量詞加上?之後以執行最少次優先(非貪婪)。

2.7.3 :068 > /a?/.match?("cde")
 => true  #0次匹配到也ok
 
2.7.3 :069 > /a/.match?("cde")
 => false
 
2.7.3 :062 > /a{3,}?/.match("aaaaaaaaa")
 => #<MatchData "aaa">
 
2.7.3 :063 > /a{3,}/.match("aaaaaaaaa")
 => #<MatchData "aaaaaaaaa">

量詞都以貪婪優先,盡可能去匹配,但加上?就是非貪婪,另外+在量詞後也有另外的效果。

/a.*b/假設有一個表達式是這樣,當我們去match``"a123b123123"時,會先抓到a後,會開始匹配.*,而整段字串都符合.*,所以其實會先記憶住a123b123123,而當繼續匹配b時,被記憶住的a123b123123,會3``2``1``3``2``1這樣釋放出來到b為止。
而當量詞加上+後,會不允許這樣的行為,將a123b123123記憶住不回朔。

整理一下

`*`:0或多次。
`+`:1或多次。
`?`:0或1次。
`{n}`:指定n次。
`{n,}`:n次或更多。
`{,m}`:m次或更少次。
`{n,m}`:最少n次,最多m次。

網路上看到一些資料寫re+re*,想半天也想不出這是什麼,其實就是量詞。
RERegular Expression的縮寫,re*等於是指一段regexp重複0或多次。

錨(指定位置)符號

^:匹配於行首。
$:匹配於行尾。

2.7.3 :111 > /^ruby/.match("gogo ruby, fight ruby")
 => nil
 
2.7.3 :112 > /^ruby/.match("ruby gogo , fight ruby")
 => #<MatchData "ruby">
#不是在行首就匹配不到。

2.7.3 :115 > /ruby$/.match("ruby gogo , fight ruby")
 => #<MatchData "ruby">
 
2.7.3 :116 > /ruby$/.match("ruby gogo , ruby fight")
 => nil
#記得$在後。

\A:匹配開頭"字串",不包括\n後的位置。
\Z:匹配结尾"字串"。可以是\n前的位置,也可以是絕對结尾。
\z:匹配结尾"字串",允許包括\n,即字符串的絕對结尾。
看code比較快

2.7.3 :194 > "\nruby love you".match(/^ruby/)
 => #<MatchData "ruby">
 
2.7.3 :195 > "\nruby love you".match(/\Aruby/)
 => nil
 
2.7.3 :196 > "ruby love you".match(/\Aruby/)
 => #<MatchData "ruby">
#\A與^感覺上\A更精準,有絕對行首的意思在。

2.7.3 :210 > "you love ruby\n".match(/ruby$/)
 => #<MatchData "ruby">
 
2.7.3 :211 > "you love \nruby".match(/ruby$/)
 => #<MatchData "ruby">
 
2.7.3 :212 > "you love ruby\n".match(/ruby\Z/)
 => #<MatchData "ruby">
 
2.7.3 :213 > "you love \nruby".match(/ruby\Z/)
 => #<MatchData "ruby">
 
2.7.3 :214 > "you love \nruby".match(/ruby\z/)
 => #<MatchData "ruby">
 
2.7.3 :215 > "you love ruby\n".match(/ruby\z/)
 => nil

#\Z與$ 沒有差太多,\z明顯更精準。

這邊先不要太去care分別,用多了自然會知道該用哪種。

\b:明確抓出單詞時用。
\B:抓出單詞,但一邊不用明確時用。
感覺自己不像在講國語!!!

2.7.3 :230 > /ruby/.match("iloverubyloveyou")
 => #<MatchData "ruby">
2.7.3 :231 > /\bruby\b/.match("iloverubyloveyou")
 => nil
2.7.3 :232 > /\bruby\b/.match("ilove ruby loveyou")
 => #<MatchData "ruby">
#單字的"邊界"

2.7.3 :233 > /\bruby\B/.match("rubyloveyou")
 => #<MatchData "ruby">
2.7.3 :234 > /\Bruby\b/.match("rubyloveyou")
 => nil
2.7.3 :235 > /\Bruby\b/.match("iloveruby")
 => #<MatchData "ruby">

2.7.3 :236 > /\Bruby\B/.match("iloverubyloveyou")
 => #<MatchData "ruby">
#沒意義

整理

`^`:匹配於行首。
`$`:匹配於行尾。
`\A`:匹配開頭"字串",不包括`\n`後的位置。
`\Z`:匹配结尾"字串"。可以是`\n`前的位置,也可以是絕對结尾。
`\z`:匹配结尾"字串",允許包括`\n`,即字符串的絕對结尾。
`\b`:這樣記,明確抓出單詞時用。
`\B`:抓出單詞,但一邊不用明確時用。

講到這又篇幅過大了...

今天再認識一些特殊用法

/(?=!)/:有一個!才匹配。

2.7.3 :244 > /ruby(?=!)/.match("ruby")
 => nil
2.7.3 :245 > /ruby(?=!)/.match("ruby!")
 => #<MatchData "ruby">
2.7.3 :246 > /ruby(?=!)/.match("iloveruby")
 => nil
2.7.3 :247 > /ruby(?=!)/.match("iloveruby!")
 => #<MatchData "ruby">

/(?!!)/:沒有!才匹配。

2.7.3 :248 > /ruby(?!!)/.match("iloveruby!")
 => nil
2.7.3 :249 > /ruby(?!!)/.match("iloveruby")
 => #<MatchData "ruby">

(?i):R後面的資料不分大小寫。

2.7.3 :253 > /R(?i)uby/.match("RUBY")
 => #<MatchData "RUBY">
2.7.3 :254 > /R(?i)uby/.match("Ruby")
 => #<MatchData "Ruby">
2.7.3 :255 > /Ruby/.match("RUBY")
 => nil

/bbb|aaa/:or(給易拼錯字的人用)。

2.7.3 :256 > /model|modle/.match("rails g modle Rail")
 => #<MatchData "modle">
2.7.3 :257 > /model|modle/.match("rails g modle Rail model")
 => #<MatchData "modle">

/mod(le|el):內部or(給易拼錯字的人用)。

2.7.3 :258 > /mod(el|le)/.match("rails g modle Rail")
 => #<MatchData "modle" 1:"le">
#用|會紀錄最先抓到的。

好像突然知道,為何Rails常有提醒我打錯字了?

/(!+|\?)/:匹配有多!個或一個?

2.7.3 :267 > /ruby(!+|\?)/.match("ruby!!!!! ruby?")
 => #<MatchData "ruby!!!!!" 1:"!!!!!">

明天就可以開始解決任何原本看不懂的天書了o 0!


今天的leetcode345. Reverse Vowels of a String
題目連結:https://leetcode.com/problems/reverse-vowels-of-a-string/
題目重點:又是一題用正規表達式會很好解的。

gsub有四種用法,除了像昨天輸入兩個參數後,會把字串裡指定的值(第一個參數),換成第二個參數。

2.7.3 :017 > "hello".gsub("l", "a")
 => "heaao"

還有只帶一個參數的話,可以成為枚舉器(迭代器)。

2.7.3 :017 > "hello".gsub("l", "a")
 => "heaao"
2.7.3 :018 > "hello".gsub("l")
 => #<Enumerator: ...>
2.7.3 :019 > "hello".gsub("l") {puts "抓到幾個,替換幾個"}
抓到幾個,替換幾個
抓到幾個,替換幾個
 => "heo"
 
2.7.3 :020 > "hello".gsub("l") { "a" }
 => "heaao"

因為這題要我們將aeiou的所在位置reverse

abci => ibca
abeci => ibeca

那我們先將母音的值與順序抓出來。

2.7.3 :021 > "hello".scan(/[aeiou]/i)
 => ["e", "o"]

如果gsub(/[aeiou]/i) { }block裡可以將["e", "o"]由後一一提供的話,那就可以替換完成了。
pop

2.7.3 :022 > x = ["a", "b", "c"]
 => ["a", "b", "c"]
2.7.3 :023 > x.pop
 => "c"
2.7.3 :024 > x
 => ["a", "b"]
2.7.3 :025 > x.pop
 => "b"
2.7.3 :026 > x
 => ["a"]
2.7.3 :027 > x.pop
 => "a"
def reverse_vowels(s)
  vowels = s.scan(/[aeiou]/i)
  s.gsub(/[aeiou]/i) { vowels.pop }
end

完成!


上一篇
D-13, Ruby 正規表達式(一) Regexp && Valid Palindrome
下一篇
D-11, Ruby 正規表達式(三) 字符 && Regular Expression Matching
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言