有没有办法在使用`String#split`进行迭代时访问最后一个匹配信息?

时间:2016-04-20 15:23:04

标签: ruby regex string iteration

我想迭代一个带有正则表达式模式的字符串。我需要迭代匹配以及它们之间的非匹配,并在迭代时访问匹配信息。

如果我不需要访问不匹配项,那么我可以使用String#scan执行此操作:

"some string".scan(/(some pattern)|(another pattern)/) do
  if $1 then ...
  elsif $2 then ...
  end
end

但我也需要迭代不匹配的部分,所以我可能需要使用String#split。但String#split不会阻止,如果我之后使用each

"some string".split(/((some pattern)|(another pattern))/).each do
  ...
end

然后,我无法访问块中的匹配信息。我想做点什么:

"some string".split(/((some pattern)|(another pattern))/) do
  if $2 then ...
  elsif $3 then ...
  else ... # access the non-matching part
  end
end

有没有办法在使用String#split进行迭代时访问最后一个匹配信息?

我可以使用scan并在正则表达式的末尾添加|(.*?)来强制它:

"some string".scan(/(some pattern)|(another pattern)|(.*?)/) do
  if $1 then ...
  elsif $2 then ...
  elsif $3 then ...
  end
end

但是使用非贪婪的匹配是非常低效的,我不能使用它。

2 个答案:

答案 0 :(得分:2)

我提出了在每个匹配周期迭代非匹配部分和匹配部分的想法。这使用scan代替split,并且将实现此目的。

s = "some string"
i = 0
s.scan(/(some pattern)|(another pattern)|\z/) do
  # Do something with the non-matching part
  ... s[i...$~.begin(0)] ... # This corresponds to the string in between
  i = $~.end(0)
  # Do something with the matching part
  if $1 then ...
  elsif $2 then ...
  end
end

答案 1 :(得分:2)

如果您只使用match一次处理字符串一次匹配,而不是像scan那样一次处理所有字符串,则可以将pre_match中的数据注入结果:

def match_all(s, r)
  match = s.match(r)

  if match
    pre_captures = [match.pre_match] + match.captures.map{nil}
    captures = [nil] + match.captures
    [pre_captures, captures] + match_all(match.post_match, r)
  else
    [[s]]
  end
end

此代码将输入字符串转换为表示[unmatched data, first match group, second match group, etc...]的元组,然后可以根据需要迭代数据:

match_all("the match information in the block", /(at)|(in)/).each do |a, b, c|
  if a
    puts "(pre: #{a})"
  elsif b
    puts "(1st: #{b})"
  elsif c
    puts "(2nd: #{c})"
  end
end

哪个输出:

(pre: the m)
(1st: at)
(pre: ch )
(2nd: in)
(pre: form)
(1st: at)
(pre: ion )
(2nd: in)
(pre:  the block)

同样的功能也可以迭代实现,如下所示:

def match_all_iter(s, r)
  s_mut = s
  all_captures = []

  loop do
    match = s_mut.match(r)

    break unless match

    pre_captures = [match.pre_match] + match.captures.map{nil}
    captures = [nil] + match.captures
    all_captures += [pre_captures, captures]

    s_mut = match.post_match
  end

  all_captures += [[s_mut]]
end