如何在Ruby中匹配正则表达式的所有出现

有没有一种快速的方法来找到Ruby中正则表达式的每个匹配?我已经查看了Ruby STL中的Regex对象,并在谷歌上进行了搜索,但一无所获。

219202 次浏览

使用scan可以做到:

string.scan(/regex/)

要找到所有匹配的字符串,请使用String的scan方法。

str = "A 54mpl3 string w1th 7 numb3rs scatter36 ar0und"
str.scan(/\d+/)
#=> ["54", "3", "1", "7", "3", "36", "0"]

如果你需要,MatchData,这是Regexp match方法返回的对象类型,使用:

str.to_enum(:scan, /\d+/).map { Regexp.last_match }
#=> [#<MatchData "54">, #<MatchData "3">, #<MatchData "1">, #<MatchData "7">, #<MatchData "3">, #<MatchData "36">, #<MatchData "0">]

使用MatchData的好处是你可以使用offset这样的方法:

match_datas = str.to_enum(:scan, /\d+/).map { Regexp.last_match }
match_datas[0].offset(0)
#=> [2, 4]
match_datas[1].offset(0)
#=> [7, 8]

如果你想了解更多,请看这些问题:

  • “# EYZ0”
  • “# EYZ0”
  • “# EYZ0”

阅读Ruby中的特殊变量$&$'$1$2也会有帮助。

如果你有一个带组的regexp:

str="A 54mpl3 string w1th 7 numbers scatter3r ar0und"
re=/(\d+)[m-t]/

你可以使用String的scan方法来找到匹配的组:

str.scan re
#> [["54"], ["1"], ["3"]]

找到匹配的模式:

str.to_enum(:scan,re).map {$&}
#> ["54m", "1t", "3r"]

您可以使用string.scan(your_regex).flatten。如果正则表达式包含组,它将以单个普通数组返回。

string = "A 54mpl3 string w1th 7 numbers scatter3r ar0und"
your_regex = /(\d+)[m-t]/
string.scan(your_regex).flatten
=> ["54", "1", "3"]

Regex也可以是命名组。

string = 'group_photo.jpg'
regex = /\A(?<name>.*)\.(?<ext>.*)\z/
string.scan(regex).flatten

你也可以使用gsub,这只是另一种方法,如果你想要匹配数据。

str.gsub(/\d/).map{ Regexp.last_match }

如果你在正则表达式中有用于其他目的的捕获组(),那么使用String#scanString#match提出的解决方案是有问题的:

  1. String#scan只得到什么是在捕捉组;
  2. String#match只得到第一个匹配,拒绝所有其他匹配;
  3. String#matches(建议函数)获得所有匹配。

在这种情况下,我们需要一个解决方案来匹配正则表达式,而不考虑捕获组。

# EYZ0

使用细化,你可以猴子修补String类,实现String#matches,这个方法将在使用细化的类的范围内可用。这是一个不可思议的方式猴补丁类在Ruby。

设置

  • # EYZ0
# This module add a String refinement to enable multiple String#match()s
# 1. `String#scan` only get what is inside the capture groups (inside the parens)
# 2. `String#match` only get the first match
# 3. `String#matches` (proposed function) get all the matches
module StringMatches
refine String do
def matches(regex)
scan(/(?<matching>#{regex})/).flatten
end
end
end


使用:# EYZ0

使用

  • # EYZ0
> require 'refinements/string_matches'


> using StringMatches


> 'function(1, 2, 3) + function(4, 5, 6)'.matches(/function\((\d), (\d), (\d)\)/)
=> ["function(1, 2, 3)", "function(4, 5, 6)"]


> 'function(1, 2, 3) + function(4, 5, 6)'.scan(/function\((\d), (\d), (\d)\)/)
=> [["1", "2", "3"], ["4", "5", "6"]]


> 'function(1, 2, 3) + function(4, 5, 6)'.match(/function\((\d), (\d), (\d)\)/)[0]
=> "function(1, 2, 3)"

返回一个MatchData对象数组

#scan非常有限——只返回一个简单的字符串数组!

获得MatchData对象数组更加强大/灵活。

我将提供两种方法(使用相同的逻辑),一种使用PORO,一种使用monkey patch:

PORO:

class MatchAll
def initialize(string, pattern)
raise ArgumentError, 'must pass a String' unless string.is_a?(String)


raise ArgumentError, 'must pass a Regexp pattern' unless pattern.is_a?(Regexp)


@string = string
@pattern = pattern
@matches = []
end


def match_all
recursive_match
end


private


def recursive_match(prev_match = nil)
index = prev_match.nil? ? 0 : prev_match.offset(0)[1]


matching_item = @string.match(@pattern, index)
return @matches unless matching_item.present?


@matches << matching_item
recursive_match(matching_item)
end
end

用法:

test_string = 'a green frog jumped on a green lilypad'


MatchAll.new(test_string, /green/).match_all
=> [#<MatchData "green", #<MatchData "green"]

猴子补丁

我通常不会容忍猴子修补,但在这种情况下:

  • 我们正在用正确的方式“隔离”。我们的补丁到它自己的模块
  • 我更喜欢这种方法,因为'string'.match_all(/pattern/)MatchAll.new('string', /pattern/).match_all更直观(而且看起来更好)
module RubyCoreExtensions
module String
module MatchAll
def match_all(pattern)
raise ArgumentError, 'must pass a Regexp pattern' unless pattern.is_a?(Regexp)


recursive_match(pattern)
end


private


def recursive_match(pattern, matches = [], prev_match = nil)
index = prev_match.nil? ? 0 : prev_match.offset(0)[1]


matching_item = self.match(pattern, index)
return matches unless matching_item.present?


matches << matching_item
recursive_match(pattern, matches, matching_item)
end
end
end
end


我建议创建一个新文件,并将补丁(假设您使用Rails)放在/lib/ruby_core_extensions/string/match_all.rb

要使用我们的补丁,我们需要使它可用:

# within application.rb
require './lib/ruby_core_extensions/string/match_all.rb'

然后确保将它包含在String类中(你可以把它放在任何你想要的地方;但是举个例子,就在我们刚刚写的require语句下面。在你include它一次之后,它将在任何地方可用,甚至在你包含它的类之外)。

String.include RubyCoreExtensions::String::MatchAll

用法:现在当你使用#match_all时,你会得到这样的结果:

test_string = 'hello foo, what foo are you going to foo today?'


test_string.match_all /foo/
=> [#<MatchData "foo", #<MatchData "foo", #<MatchData "foo"]


test_string.match_all /hello/
=> [#<MatchData "hello"]


test_string.match_all /none/
=> []

当我想匹配多个出现,然后获得关于每个出现的有用信息时,我发现这一点特别有用,比如出现开始和结束的索引(例如match.offset(0) => [first_index, last_index])