【问题标题】:How to match bar, b-a-r, b--a--r etc in a string by Regexp如何通过正则表达式匹配字符串中的 bar、b-a-r、b--a--r 等
【发布时间】:2014-06-19 21:47:52
【问题描述】:

给定一个字符串,我想找到一个单词 bar、b-a-r、b--a--r 等,其中 - 可以是任何字母。但字母之间的间隔必须相同。

所有字母都是小写,中间没有空格。

例如barbeayrqbowarprrwbxxxxxayyyyyrzzz 应与此匹配。

我试过 /b[a-z]*a[a-z]*r/ 但这匹配 bxar 这是错误的。

我想知道我是否使用正则表达式来实现这一点?

【问题讨论】:

  • 我认为一个正则表达式是不够的。
  • 您使用什么语言? (Perl 5.18 具有扩展的字符类,您可以在其中包括例如 alpha 和排除特定字符。)
  • 通过将问题分解为更小的子问题并逐步解决每个问题,您将花费更少的时间来解决问题,而不是弄清楚如何使用单个正则表达式一次性解决所有问题。 (已编辑)
  • @dystroy,您是否认真对待这是题外话,因为标签并非全部排列整齐?为什么不直接建议 shin 添加标签?
  • @CarySwoveland 因为至少三分之二的正则表达式问题是纯垃圾并且无法回答,主要是因为我们没有关于语言/库的信息。既然添加了标签(这种情况很少见),我的评论就没用了。

标签: ruby regex


【解决方案1】:

这是获取所有匹配项的一种方法。

代码

def all_matches_with_spacers(word, str)
  word_size = word.size
  word_arr = word.chars
  str_arr  = str.chars
  (0..(str.size - word_size)/(word_size-1)).each_with_object([]) do |n, arr|
    regex = Regexp.new(word_arr.join(".{#{n}}"))
    str_arr.each_cons(word_size + n * (word_size - 1))
           .map(&:join)
           .each { |substring| arr << substring if substring =~ regex }
  end
end

这需要word.size &gt; 1

示例

all_matches_with_spacers('bar',  'bar')               #=> ["bar"]
all_matches_with_spacers('bar',  'beayr')             #=> ["beayr"]
all_matches_with_spacers('bar',  'qbowarprr')         #=> ["bowarpr"]
all_matches_with_spacers('bar',  'wbxxxxxayyyyyrzzz') #=> ["bxxxxxayyyyyr"]

all_matches_with_spacers('bobo', 'bobobocbcbocbcobcodbddoddbddobddoddbddob')
  #=> ["bobo", "bobo", "bddoddbddo", "bddoddbddo"]

说明

假设

word = 'bobo'
str =  'bobobocbcbocbcobcodbddoddbddobddoddbddob'

然后

word_size = word.size  #=> 4
word_arr  = word.chars #=> ["b", "o", "b", "o"]
str_arr = str.chars
  #=> ["b", "o", "b", "o", "b", "o", "c", "b", "c", "b", "o", "c", "b", "c",
  #    "o", "b", "c", "o", "d", "b", "d", "d", "o", "d", "d", "b", "d", "d",
  #    "o", "b", "d", "d", "o", "d", "d", "b", "d", "d", "o", "b"]

如果nword的每个字母之间的间隔数,我们需要

word.size + n * (word.size - 1) <= str.size

因此(自str.size =&gt; 40),

n <= (str.size - word_size)/(word_size-1) #=> (40-4)/(4-1) => 12

因此,我们将迭代 0 到 12 个间隔:

(0..12).each_with_object([]) do |n, arr| .. end

Enumerable#each_with_object 创建一个由块变量arr 表示的初始空数组。传递给 block 的第一个值为零(分隔符),分配给 block 变量 n

然后我们有

regex = Regexp.new(word_arr.join(".{#{0}}")) #=> /b.{0}o.{0}b.{0}o/

/bar/ 相同。 wordn 间隔有长度

word_size + n * (word_size - 1) #=> 19

要提取具有此长度的str_arr 的所有子数组,我们调用:

str_arr.each_cons(word_size + n * (word_size - 1))

这里,n = 0,这是:

enum = str_arr.each_cons(4)
  #=> #<Enumerator: ["b", "o", "b", "o", "b", "o",...,"b"]:each_cons(4)>

此枚举器会将以下内容传递到其块中:

enum.to_a
  #=> [["b", "o", "b", "o"], ["o", "b", "o", "b"], ["b", "o", "b", "o"],
  #    ["o", "b", "o", "c"], ["b", "o", "c", "b"], ["o", "c", "b", "c"],
  #    ["c", "b", "c", "b"], ["b", "c", "b", "o"], ["c", "b", "o", "c"],
  #    ["b", "o", "c", "b"], ["o", "c", "b", "c"], ["c", "b", "c", "o"],
  #    ["b", "c", "o", "b"], ["c", "o", "b", "c"], ["o", "b", "c", "o"]]

接下来我们将它们转换为字符串:

ar = enum.map(&:join)
  #=> ["bobo", "obob", "bobo", "oboc", "bocb", "ocbc", "cbcb", "bcbo",
  #    "cboc", "bocb", "ocbc", "cbco", "bcob", "cobc", "obco"]

并将每个(分配给块变量substring)添加到数组arr 中:

substring =~ regex

ar.each { |substring| arr << substring if substring =~ regex }

arr => ["bobo", "bobo"]

接下来我们将间隔数增加到n = 1。这具有以下效果:

regex = Regexp.new(word_arr.join(".{#{1}}")) #=> /b.{1}o.{1}b.{1}o/
str_arr.each_cons(4 + 1 * (4 - 1))           #=> str_arr.each_cons(7)

所以我们现在检查字符串

ar = str_arr.each_cons(7).map(&:join)
  #=> ["boboboc", "obobocb", "bobocbc", "obocbcb", "bocbcbo", "ocbcboc",
  #    "cbcbocb", "bcbocbc", "cbocbco", "bocbcob", "ocbcobc", "cbcobco",
  #    "bcobcod", "cobcodb", "obcodbd", "bcodbdd", "codbddo", "odbddod",
  #    "dbddodd", "bddoddb", "ddoddbd", "doddbdd", "oddbddo", "ddbddob",
  #    "dbddobd", "bddobdd", "ddobddo", "dobddod", "obddodd", "bddoddb",
  #    "ddoddbd", "doddbdd", "oddbddo", "ddbddob"]

ar.each { |substring| arr << substring if substring =~ regex }

没有与一个分隔符匹配,因此arr 保持不变:

arr #=> ["bobo", "bobo"]

对于n = 2 垫片:

regex = Regexp.new(word_arr.join(".{#{2}}")) #=> /b.{2}o.{2}b.{2}o/
str_arr.each_cons(4 + 2 * (4 - 1))           #=> str_arr.each_cons(10)
ar = str_arr.each_cons(10).map(&:join)
  #=> ["bobobocbcb", "obobocbcbo", "bobocbcboc", "obocbcbocb", "bocbcbocbc",
  #    "ocbcbocbco", "cbcbocbcob", "bcbocbcobc", "cbocbcobco", "bocbcobcod",
  #    ...
  #    "ddoddbddob"]

ar.each { |substring| arr << substring if substring =~ regex }

arr #=> ["bobo", "bobo", "bddoddbddo", "bddoddbddo"]

没有找到超过两个间隔的匹配项,因此该方法返回

["bobo", "bobo", "bddoddbddo", "bddoddbddo"]

【讨论】:

  • 我意识到我需要将单词从 bar 更改为 others。所以这个解决方案效果很好。谢谢。解释完毕,我会看的。
【解决方案2】:

作为参考,有一个漂亮的解决方案可以解决正则表达式风格中的整体问题,它允许捕获组引用自身:

^[^b]*bar|b(?:[^a](?=[^a]*a(\1?+.)))+a\1r

遗憾的是,Ruby 不允许这样做。

有趣的位在交替的右侧。在匹配初始 b 之后,我们为 b 和 a 之间的字符定义一个非捕获组。该组将使用+ 重复。在 a 和 r 之间,我们将使用 \1` 注入捕获组 1。该组一次捕获一个字符,每次通过都会覆盖自身,因为添加了 b 和 a 之间的每个字符。

参见Quantifier Capture@CasimiretHippolyte 演示了该解决方案,他将技术背后的想法称为“qtax 技巧”。

【讨论】:

    猜你喜欢
    • 2021-07-15
    • 2015-02-20
    • 2021-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-18
    • 2012-03-18
    • 1970-01-01
    相关资源
    最近更新 更多