[英]Regex with named capture groups getting all matches in Ruby
我有一個字符串:
s="123--abc,123--abc,123--abc"
我嘗試使用Ruby 1.9的新功能“命名組”來獲取所有命名的組信息:
/(?<number>\d*)--(?<chars>\s*)/
是否有像Python的findall
這樣的API返回一個matchdata
集合? 在這種情況下,我需要返回兩個匹配,因為123
和abc
重復兩次。 每個匹配數據包含每個命名捕獲信息的詳細信息,因此我可以使用m['number']
來獲取匹配值。
命名捕獲僅適用於一個匹配結果。
Ruby的findall
類似於String#scan
。 您可以將scan
結果用作數組,也可以將塊傳遞給它:
irb> s = "123--abc,123--abc,123--abc"
=> "123--abc,123--abc,123--abc"
irb> s.scan(/(\d*)--([a-z]*)/)
=> [["123", "abc"], ["123", "abc"], ["123", "abc"]]
irb> s.scan(/(\d*)--([a-z]*)/) do |number, chars|
irb* p [number,chars]
irb> end
["123", "abc"]
["123", "abc"]
["123", "abc"]
=> "123--abc,123--abc,123--abc"
超級遲到,但這是一種復制String#scan的簡單方法,但獲取matchdata:
matches = []
foo.scan(regex){ matches << $~ }
matches
現在包含與掃描字符串相對應的MatchData對象。
您可以使用names
方法從regexp中提取已使用的變量。 所以我做的是,我使用常規scan
方法來獲取匹配,然后使用壓縮名稱和每個匹配來創建Hash
。
class String
def scan2(regexp)
names = regexp.names
scan(regexp).collect do |match|
Hash[names.zip(match)]
end
end
end
用法:
>> "aaa http://www.google.com.tr aaa https://www.yahoo.com.tr ddd".scan2 /(?<url>(?<protocol>https?):\/\/[\S]+)/
=> [{"url"=>"http://www.google.com.tr", "protocol"=>"http"}, {"url"=>"https://www.yahoo.com.tr", "protocol"=>"https"}]
我最近需要類似的東西。 這應該像String#scan
一樣工作,但返回一個MatchData對象數組。
class String
# This method will return an array of MatchData's rather than the
# array of strings returned by the vanilla `scan`.
def match_all(regex)
match_str = self
match_datas = []
while match_str.length > 0 do
md = match_str.match(regex)
break unless md
match_datas << md
match_str = md.post_match
end
return match_datas
end
end
在REPL中運行示例數據會導致以下結果:
> "123--abc,123--abc,123--abc".match_all(/(?<number>\d*)--(?<chars>[a-z]*)/)
=> [#<MatchData "123--abc" number:"123" chars:"abc">,
#<MatchData "123--abc" number:"123" chars:"abc">,
#<MatchData "123--abc" number:"123" chars:"abc">]
您可能還會發現我的測試代碼很有用:
describe String do
describe :match_all do
it "it works like scan, but uses MatchData objects instead of arrays and strings" do
mds = "ABC-123, DEF-456, GHI-098".match_all(/(?<word>[A-Z]+)-(?<number>[0-9]+)/)
mds[0][:word].should == "ABC"
mds[0][:number].should == "123"
mds[1][:word].should == "DEF"
mds[1][:number].should == "456"
mds[2][:word].should == "GHI"
mds[2][:number].should == "098"
end
end
end
@Nakilon正確顯示正則表達式的scan
,但如果你不想,你甚至不需要冒險進入正則表達式:
s = "123--abc,123--abc,123--abc"
s.split(',')
#=> ["123--abc", "123--abc", "123--abc"]
s.split(',').inject([]) { |a,s| a << s.split('--'); a }
#=> [["123", "abc"], ["123", "abc"], ["123", "abc"]]
這將返回一個數組數組,如果您有多個匹配項並且需要查看/處理它們,這將很方便。
s.split(',').inject({}) { |h,s| n,v = s.split('--'); h[n] = v; h }
#=> {"123"=>"abc"}
這將返回一個哈希值,因為元素具有相同的鍵,所以只有唯一鍵值。 當你有一堆重復的鍵但想要獨特的鍵時,這是很好的。 如果您需要與鍵相關聯的唯一值,則會出現其缺點,但這似乎是一個不同的問題。
如果使用ruby> = 1.9和命名捕獲,您可以:
class String
def scan2(regexp2_str, placeholders = {})
return regexp2_str.to_re(placeholders).match(self)
end
def to_re(placeholders = {})
re2 = self.dup
separator = placeholders.delete(:SEPARATOR) || '' #Returns and removes separator if :SEPARATOR is set.
#Search for the pattern placeholders and replace them with the regex
placeholders.each do |placeholder, regex|
re2.sub!(separator + placeholder.to_s + separator, "(?<#{placeholder}>#{regex})")
end
return Regexp.new(re2, Regexp::MULTILINE) #Returns regex using named captures.
end
end
用法(ruby> = 1.9):
> "1234:Kalle".scan2("num4:name", num4:'\d{4}', name:'\w+')
=> #<MatchData "1234:Kalle" num4:"1234" name:"Kalle">
要么
> re="num4:name".to_re(num4:'\d{4}', name:'\w+')
=> /(?<num4>\d{4}):(?<name>\w+)/m
> m=re.match("1234:Kalle")
=> #<MatchData "1234:Kalle" num4:"1234" name:"Kalle">
> m[:num4]
=> "1234"
> m[:name]
=> "Kalle"
使用分隔符選項:
> "1234:Kalle".scan2("#num4#:#name#", SEPARATOR:'#', num4:'\d{4}', name:'\w+')
=> #<MatchData "1234:Kalle" num4:"1234" name:"Kalle">
我真的很喜歡@ Umut-Utkan的解決方案,但它並沒有完全按照我想要的方式進行,所以我重寫了一下(注意,下面可能不是很漂亮的代碼,但似乎有效)
class String
def scan2(regexp)
names = regexp.names
captures = Hash.new
scan(regexp).collect do |match|
nzip = names.zip(match)
nzip.each do |m|
captgrp = m[0].to_sym
captures.add(captgrp, m[1])
end
end
return captures
end
end
現在,如果你這樣做
p '12f3g4g5h5h6j7j7j'.scan2(/(?<alpha>[a-zA-Z])(?<digit>[0-9])/)
你得到
{:alpha=>["f", "g", "g", "h", "h", "j", "j"], :digit=>["3", "4", "5", "5", "6", "7", "7"]}
(即,在一個數組中找到的所有字母字符,以及在另一個數組中找到的所有數字)。 根據您的掃描目的,這可能很有用。 無論如何,我喜歡看到只用幾行就可以輕松地重寫或擴展核心Ruby功能的例子!
一年前,我想要更容易閱讀並命名為捕獲的正則表達式,所以我對String進行了以下添加(應該可能不在那里,但當時很方便):
scan2.rb:
class String
#Works as scan but stores the result in a hash indexed by variable/constant names (regexp PLACEHOLDERS) within parantheses.
#Example: Given the (constant) strings BTF, RCVR and SNDR and the regexp /#BTF# (#RCVR#) (#SNDR#)/
#the matches will be returned in a hash like: match[:RCVR] = <the match> and match[:SNDR] = <the match>
#Note: The #STRING_VARIABLE_OR_CONST# syntax has to be used. All occurences of #STRING# will work as #{STRING}
#but is needed for the method to see the names to be used as indices.
def scan2(regexp2_str, mark='#')
regexp = regexp2_str.to_re(mark) #Evaluates the strings. Note: Must be reachable from here!
hash_indices_array = regexp2_str.scan(/\(#{mark}(.*?)#{mark}\)/).flatten #Look for string variable names within (#VAR#) or # replaced by <mark>
match_array = self.scan(regexp)
#Save matches in hash indexed by string variable names:
match_hash = Hash.new
match_array.flatten.each_with_index do |m, i|
match_hash[hash_indices_array[i].to_sym] = m
end
return match_hash
end
def to_re(mark='#')
re = /#{mark}(.*?)#{mark}/
return Regexp.new(self.gsub(re){eval $1}, Regexp::MULTILINE) #Evaluates the strings, creates RE. Note: Variables must be reachable from here!
end
end
用法示例(irb1.9):
> load 'scan2.rb'
> AREA = '\d+'
> PHONE = '\d+'
> NAME = '\w+'
> "1234-567890 Glenn".scan2('(#AREA#)-(#PHONE#) (#NAME#)')
=> {:AREA=>"1234", :PHONE=>"567890", :NAME=>"Glenn"}
筆記:
當然,將模式(例如AREA,PHONE ...)放在散列中並將帶有模式的散列添加到scan2的參數中會更優雅。
我喜歡John給出的match_all,但我認為它有錯誤。
這條線:
match_datas << md
如果正則表達式中沒有捕獲(),則有效。
此代碼提供整個行,包括正則表達式匹配/捕獲的模式。 (MatchData的[0]部分)如果正則表達式具有capture(),則該結果可能不是用戶(我)在最終輸出中想要的結果。
我認為在regex中有capture()的情況下,正確的代碼應該是:
match_datas << md[1]
match_datas的最終輸出將是從match_datas [0]開始的模式捕獲匹配數組。 如果需要正常的MatchData,這可能是預期的,其中包括match_datas [0]值,該值是整個匹配的子串,后跟match_datas [1],match_datas [[2],..這是捕獲(如果有的話) )在正則表達式模式中。
事情很復雜 - 這可能就是為什么match_all不包含在原生MatchData中的原因。
撇開Mark Hubbart的回答,我添加了以下猴子補丁:
class ::Regexp
def match_all(str)
matches = []
str.scan(self) { matches << $~ }
matches
end
end
可以用作/(?< /(?<letter>\\w)/.match_all('word')
. /(?<letter>\\w)/.match_all('word')
,並返回:
[#<MatchData "w" letter:"w">, #<MatchData "o" letter:"o">, #<MatchData "r" letter:"r">, #<MatchData "d" letter:"d">]
正如其他人所說,這依賴於在掃描塊中使用$~
作為匹配數據。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.