ChatGPT解决这个技术问题 Extra ChatGPT

匹配平衡括号的正则表达式

我需要一个正则表达式来选择两个外括号之间的所有文本。

示例:
START_TEXT(text here(possible text)text(possible text(more text)))END_TXT
^ ^

结果:
(text here(possible text)text(possible text(more text)))

这个问题很差,因为不清楚它在问什么。所有的答案都有不同的解释。 @DaveF 你能澄清一下这个问题吗?
在这篇文章中回答:stackoverflow.com/questions/6331065/…

b
bobble bubble

我想添加此答案以供快速参考。随时更新。

.NET 正则表达式 使用 balancing groups

\((?>\((?<c>)|[^()]+|\)(?<-c>))*(?(c)(?!))\)

其中 c 用作深度计数器。

Demo at Regexstorm.com

Stack Overflow:使用 RegEx 平衡匹配括号

Wes 令人费解的博客:使用 .NET 正则表达式匹配平衡结构

Greg Reinacker 的博客:正则表达式中的嵌套构造

PCRE 使用 recursive pattern

\((?:[^)(]+|(?R))*+\)

Demo at regex101;或没有交替:

\((?:[^)(]*(?R)?)*+\)

Demo at regex101;或 unrolled 以获得性能:

\([^)(]*+(?:(?R)[^)(]*)*+\)

Demo at regex101;该模式粘贴在代表 (?0)(?R) 处。

Perl、PHP、Notepad++、 Rperl=TRUEPythonRegex package(?V1) 用于 Perl 行为。

Ruby 使用 subexpression calls

在 Ruby 2.0 中,\g<0> 可用于调用完整模式。

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

Demo at Rubular; Ruby 1.9 仅支持 capturing group recursion

(\((?>[^)(]+|\g<1>)*\))

Demo at Rubular  (从 Ruby 1.9.3 开始的atomic grouping

JavaScript  API :: XRegExp.matchRecursive

XRegExp.matchRecursive(str, '\\(', '\\)', 'g');

JS、Java 和其他没有递归的正则表达式风格,最多 2 级嵌套:

\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)

Demo at regex101。更深入的 nesting needs to be added 模式。
在不平衡括号 drop the + quantifier. 上更快地失败

Java:一个有趣的idea using forward references by @jaytea

Reference - What does this regex mean?

rexegg.com - 递归正则表达式

Regular-Expressions.info - 正则表达式递归


当您使用所有格量词重复一个组时,使该组原子化是没有用的,因为该组中的所有回溯位置在每次重复时都会被删除。所以写 (?>[^)(]+|(?R))*+ 与写 (?:[^)(]+|(?R))*+ 是一样的。下一个模式也是如此。关于展开的版本,您可以在此处放置一个所有格量词:[^)(]*+ 以防止回溯(如果没有右括号)。
@CasimiretHippolyte 谢谢!我调整了 PCRE 模式,对于 Ruby 1.9,你的意思是整个模式都是 like this 吗?请随时更新自己。我明白你的意思,但不确定是否有很大的改进。
如果有人需要 .NET 的大括号版本:\{(?>\{(?<c>)|[^{}]+|\}(?<-c>))*(?(c)(?!))\}
对于递归,我会推荐 (\((?:[^)(]+|(?1))*+\))(或 ?2?3 等,具体取决于它是哪个数字组)而不是 \((?:[^)(]+|(?R))*+\)?R 总是递归回到表达式的开头。如果您单独使用它,那很好。但是例如,如果您在 if 语句之后发现逻辑比较,则 if \((?:[^)(]+|(?R))*+\) 将不会匹配任何内容,因为 if 也必须重复才能匹配,而不仅仅是括号。但是,if (\((?:[^)(]+|(?1))*+\)) 只会检查一次 if,然后递归检查第一组。
@bobblebubble 好点。如果我把它扔掉,为什么要捕获第三组呢?总是有很多方法可以用 RegEx 给同一只猫剥皮。
M
Makyen

正则表达式对于这项工作来说是错误的工具,因为您正在处理嵌套结构,即递归。

但是有一个简单的算法可以做到这一点,我在 in this answerprevious question 中进行了更详细的描述。要点是编写代码来扫描字符串,保持一个开括号的计数器,这些开括号还没有被右括号匹配。当该计数器归零时,您就知道您已到达最后的右括号。


.NET 的实现有[平衡组定义msdn.microsoft.com/en-us/library/…,它允许这种事情。
我不同意正则表达式是错误的工具,原因有几个。 1)大多数正则表达式实现都有一个可行的解决方案,如果不是完美的解决方案。 2) 通常你试图在其他非常适合正则表达式的标准也在起作用的上下文中找到平衡的分隔符对。 3) 通常你将一个正则表达式传递给一些只接受正则表达式并且你别无选择的 API。
正则表达式是这项工作的正确工具。这个答案是不对的。请参阅 rogal111 的答案。
完全同意答案。尽管 regexp 中有一些递归实现,但它们等同于有限状态机,并且不支持使用嵌套结构,但上下文无关语法可以做到这一点。看看 Homsky 的形式语法层次结构。
弗兰克是对的,上下文无关语法不能用正则表达式来描述。这是这个答案的关键点。
A
Amal Murali

您可以使用 regex recursion

\(([^()]|(?R))*\)

一个例子在这里真的很有用,我不能让它适用于像“(1,(2,3))(4,5)”这样的东西。
@AndyHayden 这是因为 "(1, (2, 3)) (4, 5)" 有两组用空格隔开。将我的正则表达式与全局标志一起使用:/(([^()]|(?R))*)/g。这是在线测试:regex101.com/r/lF0fI1/1
我上周问了一个关于这个的问题stackoverflow.com/questions/26385984/recursive-pattern-in-regex
在 .NET 4.5 中,我收到此模式的以下错误:Unrecognized grouping construct
惊人的!这是正则表达式的一大特色。感谢您成为唯一真正回答问题的人。此外,那个 regex101 网站很不错。
C
Community
[^\(]*(\(.*\))[^\)]*

[^\(]* 匹配字符串开头不是左括号的所有内容,(\(.*\)) 捕获括在括号中的所需子字符串,[^\)]* 匹配字符串末尾不是右括号的所有内容。请注意,此表达式不会尝试匹配括号;一个简单的解析器(见 dehmann's answer)会更适合。


类内的括号不需要转义。因为在里面它不是一个metacharacted。
这个 expr 对返回“(text)text(text)”之类的“text(text)text(text)text”失败。正则表达式不能计算括号。
A
Alan Moore
(?<=\().*(?=\))

如果您想在两个匹配的括号之间选择文本,那么您对正则表达式不走运。这是不可能的(*)。

此正则表达式仅返回字符串中第一个左括号和最后一个右括号之间的文本。

(*) 除非您的正则表达式引擎具有 balancing groups or recursion 等功能。支持此类功能的引擎数量正在缓慢增长,但它们仍然不是普遍可用的。


“<=”和“=”符号是什么意思?这个表达式的目标是什么正则表达式引擎?
这是环视,或更准确地说是“零宽度前瞻/后瞻断言”。大多数现代正则表达式引擎都支持它们。
根据 OP 的示例,他希望在匹配中包含最外层的括号。这个正则表达式把它们扔掉了。
@Alan M:你是对的。但是根据问题文本,他想要最外面的括号之间的所有内容。选择您的选择。他说他已经尝试了几个小时,所以甚至没有考虑“包括最外面的括号在内的所有内容”作为意图,因为它是如此微不足道:“(。*)”。
@ghayes 答案来自 2009 年。那是很久以前的事了;允许某种形式的递归的正则表达式引擎比现在更不常见(而且它们仍然非常不常见)。我会在我的回答中提到它。
S
Somnath Musib

这个答案解释了为什么正则表达式不是这个任务的正确工具的理论限制。

正则表达式不能做到这一点。

正则表达式基于称为 Finite State Automata (FSA) 的计算模型。顾名思义,FSA 只能记住当前状态,它没有关于先前状态的信息。

https://i.stack.imgur.com/XqtHd.png

在上图中,S1 和 S2 是两个状态,其中 S1 是开始和最后一步。因此,如果我们尝试使用字符串 0110 ,则转换如下:

      0     1     1     0
-> S1 -> S2 -> S2 -> S2 ->S1

在上述步骤中,当我们在第二个S2,即在解析 011001 之后,FSA 没有关于 01 中前一个 0 的信息,因为它只能记住当前状态和下一个状态输入符号。

在上面的问题中,我们需要知道左括号的编号;这意味着它必须存储在某个地方。但是由于 FSAs 不能这样做,所以不能编写正则表达式。

但是,可以编写一个算法来完成这项任务。算法通常属于 Pushdown Automata (PDA)PDAFSA 高一级。 PDA 有一个额外的堆栈来存储一些额外的信息。 PDA 可以用来解决上述问题,因为我们可以'push' 堆栈中的左括号,并在遇到右括号时使用'pop'。如果最后堆栈为空,则左括号和右括号匹配。否则不行。


这里有几个答案,这证明了,这是可能的。
@Marco这个答案从理论角度讨论了正则表达式。现在许多正则表达式引擎不仅依赖这个理论模型,而且使用一些额外的内存来完成这项工作!
@JiříHerník:这些不是严格意义上的正则表达式:Kleene 没有将其定义为正则表达式。一些正则表达式引擎确实实现了一些额外的功能,使它们不仅仅解析正则语言。
这应该是一个公认的答案。不幸的是,许多“开发人员”没有接受过适当的 Comp Sc/Eng 教育,并且不知道诸如停机问题、抽水引理等主题......
A
Alan Moore

使用 .NET 正则表达式实际上可以做到这一点,但这并不简单,因此请仔细阅读。

您可以阅读一篇不错的文章 here。您可能还需要阅读 .NET 正则表达式。您可以开始阅读here

使用尖括号 <> 是因为它们不需要转义。

正则表达式如下所示:

<
[^<>]*
(
    (
        (?<Open><)
        [^<>]*
    )+
    (
        (?<Close-Open>>)
        [^<>]*
    )+
)*
(?(Open)(?!))
>

M
Manish

我也陷入了嵌套模式出现的这种情况。

正则表达式是解决上述问题的正确方法。使用下面的模式

'/(\((?>[^()]+|(?1))*\))/'

作为在类似主题上寻求帮助的用户,我不知道该正则表达式具体做什么以及如何使用它来将其应用于我自己的问题。也许这是一个很好的答案,但鉴于正则表达式的本质是神秘的,我必须查看它的每一部分,看看这是否对我有帮助。鉴于这种“解决方案”有很多答案,我想我不会。
M
Marco

这是最终的正则表达式:

\(
(?<arguments> 
(  
  ([^\(\)']*) |  
  (\([^\(\)']*\)) |
  '(.*?)'

)*
)
\)

例子:

input: ( arg1, arg2, arg3, (arg4), '(pip' )

output: arg1, arg2, arg3, (arg4), '(pip'

请注意,'(pip' 已正确管理为字符串。 (在调节器中试过:http://sourceforge.net/projects/regulator/


如果没有嵌套或者你只关心最里面的组,我喜欢这种技术。它不依赖递归。我能够使用它来提取包含括号的参数。我在 Regex101 做了一个工作示例
P
Peter Mortensen

我编写了一个名为 balanced 的小型 JavaScript 库来帮助完成这项任务。您可以通过执行此操作

balanced.matches({
    source: source,
    open: '(',
    close: ')'
});

您甚至可以进行替换:

balanced.replacements({
    source: source,
    open: '(',
    close: ')',
    replace: function (source, head, tail) {
        return head + source + tail;
    }
});

下面是一个更复杂的交互式示例 JSFiddle


n
nhahtdh

使用 Ruby 的正则表达式(1.9.3 或更高版本):

/(?<match>\((?:\g<match>|[^()]++)*\))/

Demo on rubular


W
Wiktor Stribiżew

除了 bobble bubble's answer,还有其他支持递归构造的正则表达式风格。

卢阿

使用 %b()%b{} / %b[] 用于大括号/方括号):

for s in string.gmatch("Extract (a(b)c) and ((d)f(g))", "%b()") do print(s) end (见demo)

Raku(前 Perl6):

不重叠的多个平衡括号匹配:

my regex paren_any { '(' ~ ')' [ <-[()]>+ || <&paren_any> ]* }
say "Extract (a(b)c) and ((d)f(g))" ~~ m:g/<&paren_any>/;
# => (「(a(b)c)」 「((d)f(g))」)

重叠多个平衡括号匹配:

say "Extract (a(b)c) and ((d)f(g))" ~~ m:ov:g/<&paren_any>/;
# => (「(a(b)c)」 「(b)」 「((d)f(g))」 「(d)」 「(g)」)

请参阅demo

Python re 非正则表达式解决方案

How to get an expression between balanced parentheses 参见 poke's answer

Java 可定制的非正则表达式解决方案

这是一个可定制的解决方案,允许在 Java 中使用单字符文字分隔符:

public static List<String> getBalancedSubstrings(String s, Character markStart, 
                                 Character markEnd, Boolean includeMarkers) 

{
        List<String> subTreeList = new ArrayList<String>();
        int level = 0;
        int lastOpenDelimiter = -1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == markStart) {
                level++;
                if (level == 1) {
                    lastOpenDelimiter = (includeMarkers ? i : i + 1);
                }
            }
            else if (c == markEnd) {
                if (level == 1) {
                    subTreeList.add(s.substring(lastOpenDelimiter, (includeMarkers ? i + 1 : i)));
                }
                if (level > 0) level--;
            }
        }
        return subTreeList;
    }
}

示例用法:

String s = "some text(text here(possible text)text(possible text(more text)))end text";
List<String> balanced = getBalancedSubstrings(s, '(', ')', true);
System.out.println("Balanced substrings:\n" + balanced);
// => [(text here(possible text)text(possible text(more text)))]

请参阅 online Java demo 以证明它适用于多个匹配项。
C
Community

答案取决于您是否需要匹配匹配的括号集,或者仅匹配输入文本中的第一个打开到最后一个关闭。

如果您需要匹配匹配的嵌套括号,那么您需要的不仅仅是正则表达式。 - 见@dehmann

如果它只是第一次打开到最后一次关闭,请参阅 @Zach

决定你想要发生的事情:

abc ( 123 ( foobar ) def ) xyz ) ghij

在这种情况下,您需要确定您的代码需要匹配的内容。


这不是一个答案。
是的,要求改变问题应该作为评论给出,
G
Gene Olson
"""
Here is a simple python program showing how to use regular
expressions to write a paren-matching recursive parser.

This parser recognises items enclosed by parens, brackets,
braces and <> symbols, but is adaptable to any set of
open/close patterns.  This is where the re package greatly
assists in parsing. 
"""

import re


# The pattern below recognises a sequence consisting of:
#    1. Any characters not in the set of open/close strings.
#    2. One of the open/close strings.
#    3. The remainder of the string.
# 
# There is no reason the opening pattern can't be the
# same as the closing pattern, so quoted strings can
# be included.  However quotes are not ignored inside
# quotes.  More logic is needed for that....


pat = re.compile("""
    ( .*? )
    ( \( | \) | \[ | \] | \{ | \} | \< | \> |
                           \' | \" | BEGIN | END | $ )
    ( .* )
    """, re.X)

# The keys to the dictionary below are the opening strings,
# and the values are the corresponding closing strings.
# For example "(" is an opening string and ")" is its
# closing string.

matching = { "(" : ")",
             "[" : "]",
             "{" : "}",
             "<" : ">",
             '"' : '"',
             "'" : "'",
             "BEGIN" : "END" }

# The procedure below matches string s and returns a
# recursive list matching the nesting of the open/close
# patterns in s.

def matchnested(s, term=""):
    lst = []
    while True:
        m = pat.match(s)

        if m.group(1) != "":
            lst.append(m.group(1))

        if m.group(2) == term:
            return lst, m.group(3)

        if m.group(2) in matching:
            item, s = matchnested(m.group(3), matching[m.group(2)])
            lst.append(m.group(2))
            lst.append(item)
            lst.append(matching[m.group(2)])
        else:
            raise ValueError("After <<%s %s>> expected %s not %s" %
                             (lst, s, term, m.group(2)))

# Unit test.

if __name__ == "__main__":
    for s in ("simple string",
              """ "double quote" """,
              """ 'single quote' """,
              "one'two'three'four'five'six'seven",
              "one(two(three(four)five)six)seven",
              "one(two(three)four)five(six(seven)eight)nine",
              "one(two)three[four]five{six}seven<eight>nine",
              "one(two[three{four<five>six}seven]eight)nine",
              "oneBEGINtwo(threeBEGINfourENDfive)sixENDseven",
              "ERROR testing ((( mismatched ))] parens"):
        print "\ninput", s
        try:
            lst, s = matchnested(s)
            print "output", lst
        except ValueError as e:
            print str(e)
    print "done"

P
Peter Mortensen

您需要第一个和最后一个括号。使用这样的东西:

str.indexOf('('); - 它会给你第一次出现

str.lastIndexOf(')'); - 最后一个

所以你需要一个字符串,

String searchedString = str.substring(str1.indexOf('('),str1.lastIndexOf(')');

c
crapthings

因为 js 正则表达式不支持递归匹配,所以我无法使平衡括号匹配工作。

所以这是一个简单的 javascript for 循环版本,它将“method(arg)”字符串转换为数组

push(number) map(test(a(a()))) bass(wow, abc)
$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)
const parser = str => {
  let ops = []
  let method, arg
  let isMethod = true
  let open = []

  for (const char of str) {
    // skip whitespace
    if (char === ' ') continue

    // append method or arg string
    if (char !== '(' && char !== ')') {
      if (isMethod) {
        (method ? (method += char) : (method = char))
      } else {
        (arg ? (arg += char) : (arg = char))
      }
    }

    if (char === '(') {
      // nested parenthesis should be a part of arg
      if (!isMethod) arg += char
      isMethod = false
      open.push(char)
    } else if (char === ')') {
      open.pop()
      // check end of arg
      if (open.length < 1) {
        isMethod = true
        ops.push({ method, arg })
        method = arg = undefined
      } else {
        arg += char
      }
    }
  }

  return ops
}

// const test = parser(`$$(groups) filter({ type: 'ORGANIZATION', isDisabled: { $ne: true } }) pickBy(_id, type) map(test()) as(groups)`)
const test = parser(`push(number) map(test(a(a()))) bass(wow, abc)`)

console.log(test)

结果就像

[ { method: 'push', arg: 'number' },
  { method: 'map', arg: 'test(a(a()))' },
  { method: 'bass', arg: 'wow,abc' } ]
[ { method: '$$', arg: 'groups' },
  { method: 'filter',
    arg: '{type:\'ORGANIZATION\',isDisabled:{$ne:true}}' },
  { method: 'pickBy', arg: '_id,type' },
  { method: 'map', arg: 'test()' },
  { method: 'as', arg: 'groups' } ]

P
Prakhar Agrawal

虽然很多答案以某种形式提到了这一点,说正则表达式不支持递归匹配等,但其主要原因在于计算理论的根源。

{a^nb^n | n>=0} is not regular 形式的语言。正则表达式只能匹配构成常规语言集的一部分的事物。

阅读更多@here


D
Daniel

我没有使用正则表达式,因为它很难处理嵌套代码。所以这个片段应该能够让你抓住带有平衡括号的代码部分:

def extract_code(data):
    """ returns an array of code snippets from a string (data)"""
    start_pos = None
    end_pos = None
    count_open = 0
    count_close = 0
    code_snippets = []
    for i,v in enumerate(data):
        if v =='{':
            count_open+=1
            if not start_pos:
                start_pos= i
        if v=='}':
            count_close +=1
            if count_open == count_close and not end_pos:
                end_pos = i+1
        if start_pos and end_pos:
            code_snippets.append((start_pos,end_pos))
            start_pos = None
            end_pos = None

    return code_snippets

我用它从文本文件中提取代码片段。


T
TOPKAT

这并没有完全解决 OP 问题,但我认为它可能对一些来这里搜索嵌套结构正则表达式的人有用:

从javascript中的函数字符串(具有嵌套结构)解析参数

https://i.stack.imgur.com/fiUlC.png

匹配括号、方括号、圆括号、单引号和双引号

Here you can see generated regexp in action

/**
 * get param content of function string.
 * only params string should be provided without parentheses
 * WORK even if some/all params are not set
 * @return [param1, param2, param3]
 */
exports.getParamsSAFE = (str, nbParams = 3) => {
    const nextParamReg = /^\s*((?:(?:['"([{](?:[^'"()[\]{}]*?|['"([{](?:[^'"()[\]{}]*?|['"([{][^'"()[\]{}]*?['")}\]])*?['")}\]])*?['")}\]])|[^,])*?)\s*(?:,|$)/;
    const params = [];
    while (str.length) { // this is to avoid a BIG performance issue in javascript regexp engine
        str = str.replace(nextParamReg, (full, p1) => {
            params.push(p1);
            return '';
        });
    }
    return params;
};

K
Kishor Patil

这可能有助于匹配平衡括号。

\s*\w+[(][^+]*[)]\s*

H
Hamid Mir

这个也有效

re.findall(r'\(.+\)', s)