【问题标题】:Ruby - Regex for matching brackets?Ruby - 匹配括号的正则表达式?
【发布时间】:2014-09-22 17:00:25
【问题描述】:

我试图确定一个字符串是否有正确的右括号。

为此,我使用以下三个括号对。

[]
()
{}

只要格式正确,括号也可以嵌套。

)([]{} - Does not have properly closed brackets because )( is reverse order

[()] - Does contain properly closed brackets.

我尝试过使用正则表达式,经过一番摸索,我明白了。

[^\(\[]*(\(.*\))[^\)\]]*

但是,这样做存在一些问题。

只匹配括号,不匹配括号

我不明白为什么它不匹配括号。

在我的示例中,我清楚地在括号前使用了反斜杠。

输入

[] - true
[()] - true (nested brackets but they match properly)
{} - true
}{ - false (brackets are wrong direction)
}[]} - false (brackets are wrong direction)
[[]] - true (nested brackets but they match properly

【问题讨论】:

  • 你向我们展示一些输入和预期输出如何。
  • @Joh regex 是不可能完成的任务。您需要使用此输入更新您的问题([][()]{}{{}[]})
  • @AvinashRaj 你完全错了。
  • @AvinashRaj 这是可能的。
  • 请阅读答案。我发布了基于递归正则表达式的工作解决方案。顺便说一句,正则表达式解决方案也可以在 O(n) 时间内工作,尽管可能有更大的常数。

标签: ruby regex brackets


【解决方案1】:
non_delimiters = /[^(){}\[\]]*/
Paired = /\(#{non_delimiters}\)|\{#{non_delimiters}\}|\[#{non_delimiters}\]/
Delimiter = /[(){}\[\]]/

def balanced? s
  s = s.dup
  s.gsub!(Paired, "".freeze) while s =~ Paired
  s !~ Delimiter
end

balanced?(")([]{}")
# => false
balanced?("[]")
# => true
balanced?("[()]")
# => true
balanced?("{}")
# => true
balanced?("}{")
# => false
balanced?("}[]}")
# => false
balanced?("[[]]")
# => true

【讨论】:

  • 你能解释一下Object#freeze的原因吗?您的示例似乎无关紧要。
  • @CarySwoveland 最近 Ruby 的一个特性优化了字符串生成。每当有一个字符串文字在其上应用了freeze 时,该字符串只会在任何时候被读取时生成一次。
  • 您可以在 ruby​​ 2.x 中的文件的顶行添加魔术注释 # frozen_string_literal: true 以自动为文件中的所有字符串文字执行此操作
【解决方案2】:

这对于正则表达式来说可能是一个糟糕的用例,我会使用一个简单的堆栈解析器。

def matching_brackets?(a_string)
  brackets =  {'[' => ']', '{' => '}', '(' => ')'}
  lefts = brackets.keys
  rights = brackets.values
  stack = []
  a_string.each_char do |c|
    if lefts.include? c
      stack.push c
    elsif rights.include? c
      return false if stack.empty?
      return false unless brackets[stack.pop].eql? c
    end
  end
  stack.empty?
end

matching_brackets? "[]"
matching_brackets? "[()]"
matching_brackets? "{}"
matching_brackets? "}{"
matching_brackets? "}[]}"
matching_brackets? "[[]]"
matching_brackets? "[[{]}]"

编辑:Cary Swoveland - 编写实际代码并让人们批评它:-?。

更新:有一个讨厌的小错误,我检查结束字符是否与开始字符匹配。修好了!

【讨论】:

  • 是的,杰夫,这就是这样做的方法,但是为什么在实际代码如此短的情况下使用伪代码呢? +1 如果您进行编辑以提供代码。
  • 编写自己的解析器在性能方面可能很好,但使用递归正则表达式需要更少的努力。
  • 在我的回答中为解析器添加了 ruby​​ 代码。我花了不到 5 分钟的时间来写。堆栈解析器非常简单,递归正则表达式模式会吓坏 99% 的开发人员:)
  • 正如承诺的那样,+1。批评是好的。免费批评更好。
  • @jeff 正是我正在寻找的...感谢简单的解决方案
【解决方案3】:

根据this article,Ruby 2.0 版支持递归正则表达式。这意味着您可以使用特定于 Ruby 的令牌 \g<0> 在您的正则表达式的任何点递归匹配整个正则表达式。这种方法可以有效地模拟堆栈以解决您的任务。

这是生成的正则表达式: [^(){}\[\]]*(\((\g<0>)?\)|\{(\g<0>)?\}|\[(\g<0>)?\])?[^(){}\[\]]*

此更新版本处理如下情况:[(){}],当多个括号组处于同一级别时。感谢@Jonny 5 指出这个案例:

 [^(){}\[\]]*((\((\g<0>)?\)|\{(\g<0>)?\}|\[(\g<0>)?\])?[^(){}\[\]]*)*

这个表达式需要检查整个输入字符串是否匹配。部分匹配意味着在字符串的某个点的括号排序存在错误。

这是不需要检查整个输入字符串是否匹配的其他版本:

 \A([^(){}\[\]]*((\((\g<1>)?\)|\{(\g<1>)?\}|\[(\g<1>)?\])?[^(){}\[\]]*)*)\Z

您可能会注意到它尝试匹配相应的括号对,然后递归匹配自身。我已经尝试过here,它似乎有效。我不是 Ruby 工程师,所以我无法运行实际的 Ruby 测试,但希望没有必要。

【讨论】:

  • @sawa,正式地说,对 Ruby 2.0 版支持的整个正则表达式的递归调用。从 1.9 Ruby can recourse only specific groups by number or name 开始。因此,在我的示例中,您必须将整个正则表达式包装在组 () 中并使用 \g&lt;1&gt; 而不是 \g&lt;0&gt;
  • 很酷,每天都学点新东西。然而,值得注意的是,在我必须维护的代码中发现该正则表达式可能会对我自己或 git blame 中的人造成严重后果。
  • ((.)(.)) 不匹配,或者我们说它“匹配”,意思是 truetrue 也适用于 ([。它只处理(a[b]) 之类的东西,但不处理(a[b][b])。但是,由于提到递归而加分。
  • @Jonny5 谢谢,我完全错过了几个括号组一个接一个的情况。我会更正正则表达式。
  • 我的意思是当我设置r 等于你的正则表达式"][" =~ r #=&gt; 0,这意味着"][" 匹配,从偏移量0 开始,它不应该匹配。它匹配一个空子字符串:"]["[r] #=&gt; "".
【解决方案4】:

我假设您的字符串仅包含字符串"()[]{}" 中的字符。请注意,对于字符串str 来满足匹配要求:

  • str 必须为空或包含子字符串"()""[]""[]";和
  • 如果str 不为空,则str"()""[]""[]" 删除满足匹配要求。

因此,我们可以顺序删除子字符串对,直到我们不能再这样做为止。如果剩下的为空,则原始字符串满足匹配要求;否则它不会:

def matching?(str)
  return true if str.empty?
  s = str.gsub(/\(\)|\[\]|\{\}/,"")
  return false if s == str
  matching?(s)
end

matching?(")([]{}")         #=> false
matching?("[()]")           #=> true
matching?("[()[{()}]{()}]") #=> true 

【讨论】:

  • 修改方法以允许字符串中包含其他字符非常简单。
  • 当然可以,但应该比 jeff price 算法描述慢得多,后者只需要查看字符串的每个字符一次。
  • 没错,@ascar。使用堆栈是解决这个问题的自然方法,但是当我遇到这个问题时已经有人建议了,所以我想我会展示一种不同的方法。
【解决方案5】:

regex 并不意味着验证字符串中的正确语法,因此非常不适合这种情况。正则表达式是一种在文本中查找模式的工具。

你应该使用解析器。

这是用于执行此操作的堆栈解析器的 ruby​​ 代码:

def validBrackets?(str)
  stack = []
  str.each_char do |char|
    case char
    when '{', '[', '('
      stack.push(char)
    when '}'
      x = stack.pop
      return false if x != '{'
    when ']'
      x = stack.pop
      return false if x != '['
    when ')'
      x = stack.pop
      return false if x != '('
    end
  end
  stack.empty?
end

【讨论】:

  • 我很抱歉。我将编辑这个问题以提出另一个解决方案。我认为正则表达式是解决这个问题的正确方法
  • 您可以使用case 语句使您的代码看起来更好。但是StringScanner 会更有效率。
  • @sawa case 在这种情况下并没有让它变得更好,因为我必须将第一个 if 拆分为 3 个 when 子句或使用 case char == 语法,基本上看起来与 if 相同否则
  • 没有。您可以用逗号将它们放在一起。 (或者,你可以有一个覆盖三个字符的正则表达式。)
  • @sawa 不知道这一点。为case语句编辑并使用each_char,因为它应该比首先创建char数组快一点(尽管它可能与底层C实现相同,其中字符串无论如何都是char数组)
【解决方案6】:

编辑:根据 Jonny 5 的建议移至顶部
在阅读了下面的 cmets 并受到 Aivean 解决方案的启发后,这是一个修改后的模式
(\[([^][)({}]|\g&lt;0&gt;)*\])|\(\g&lt;2&gt;*\)|\{\g&lt;2&gt;*\}


如果您的正则表达式引擎支持递归,我建议使用 3 种不同的模式作为过滤器,如果您的输入通过了所有这三种模式,那就是很好的匹配

([(?:[^][]|(?R))]) # match nested [] 
(((?:[^)(]|(?R)))) # match nested ()
({(?:[^{}]|(?R))*}) # match nested {}

【讨论】:

  • 我猜你不会找到像 ({)} 这样的不匹配项。
  • @ascar 我喜欢你的评论。您能否对所有使用正则表达式的答案发表上述评论?
  • 我认为你应该把最后的模式放在你的答案之上,它真的很整洁,很容易被忽视。如果是这样,会添加start and end :)
【解决方案7】:

好吧,我想通了。

def valid_string?(brace)
  stack = []
  brackets = { '{' => '}', '[' => ']', '(' => ')' }
  brace.each_char do |char|
    stack << char if brackets.key?(char)
    return false if brackets.key(char) && brackets.key(char) != stack.pop
  end
  stack.empty?
end

【讨论】:

    【解决方案8】:

    我不明白你怎么能期望你的正则表达式匹配括号。这是您的正则表达式的作用:

    [^\(\[]*  # Match any number of characters except ( or [
    (         # Start capturing group:
     \(       # Match ( 
     .*       # Match any number of characters (except linebreaks)
     \)       # Match )
    )         # End of capturing group
    [^\)\]]*  # Match any number of characters except ) or ]
    

    【讨论】:

    • 我认为op的需求完全不同。
    • @AvinashRaj:是的,这就是我回答的重点——向他解释他的正则表达式(因为他问为什么他的正则表达式不不匹配)。这与他使用的正则表达式相同,只是进行了注释。