我正在尝试匹配具有嵌套括号的数学表达式字符串。
import re
p = re.compile('\(.+\)')
str = '(((1+0)+1)+1)'
print p.findall(s)
[ '(((1 + 0)+1)+1)']
我希望它匹配所有包含的表达式,例如(1 + 0),((1 + 0)+1)......
我甚至不关心它是否匹配不需要的那些(((1 + 0),我可以照顾那些。
为什么它不会那样做,我该怎么办呢?
答案 0 :(得分:25)
正如其他人所提到的,正则表达式不是嵌套构造的方法。我将使用pyparsing:
给出一个基本示例import pyparsing # make sure you have this installed
thecontent = pyparsing.Word(pyparsing.alphanums) | '+' | '-'
parens = pyparsing.nestedExpr( '(', ')', content=thecontent)
这是一个用法示例:
>>> parens.parseString("((a + b) + c)")
<强>输出:强>
( # all of str
[
( # ((a + b) + c)
[
( # (a + b)
['a', '+', 'b'], {}
), # (a + b) [closed]
'+',
'c'
], {}
) # ((a + b) + c) [closed]
], {}
) # all of str [closed]
(手动完成换行/缩进/评论)
编辑:根据Paul McGuire的建议修改以消除不必要的Forward
。
以嵌套列表格式获取输出:
res = parens.parseString("((12 + 2) + 3)")
res.asList()
<强>输出:强>
[[['12', '+', '2'], '+', '3']]
答案 1 :(得分:14)
有a new regular engine module准备替换Python中的现有版本。它引入了许多新功能,包括递归调用。
import regex
s = 'aaa(((1+0)+1)+1)bbb'
result = regex.search(r'''
(?<rec> #capturing group rec
\( #open parenthesis
(?: #non-capturing group
[^()]++ #anyting but parenthesis one or more times without backtracking
| #or
(?&rec) #recursive substitute of group rec
)*
\) #close parenthesis
)
''',s,flags=regex.VERBOSE)
print(result.captures('rec'))
输出:
['(1+0)', '((1+0)+1)', '(((1+0)+1)+1)']
regex
中的相关错误:http://code.google.com/p/mrab-regex-hg/issues/detail?id=78
答案 2 :(得分:12)
正则表达式尝试尽可能多地匹配文本,从而消耗所有字符串。它不会在该字符串的某些部分上查找正则表达式的其他匹配项。这就是为什么你只得到一个答案。
解决方案是不使用正则表达式。如果您实际上正在尝试解析数学表达式,请使用真正的解析解决方案。如果你真的只想捕获括号内的碎片,只需在你看到(和)时递增字符计数并递增一个递减计数器。
答案 3 :(得分:11)
正则表达式语言不足以匹配任意嵌套的结构。为此,您需要一个下推式自动机(即解析器)。有几种此类工具可用,例如PLY。
Python还为自己的语法提供parser library,这可能会满足您的需要。然而,输出非常详细,并需要一段时间来包裹你的头。如果你对这个角度感兴趣,下面的讨论试图尽可能简单地解释。
>>> import parser, pprint
>>> pprint.pprint(parser.st2list(parser.expr('(((1+0)+1)+1)')))
[258,
[327,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[7, '('],
[320,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[7, '('],
[320,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[7,
'('],
[320,
[304,
[305,
[306,
[307,
[308,
[310,
[311,
[312,
[313,
[314,
[315,
[316,
[317,
[318,
[2,
'1']]]]],
[14,
'+'],
[315,
[316,
[317,
[318,
[2,
'0']]]]]]]]]]]]]]]],
[8,
')']]]]],
[14,
'+'],
[315,
[316,
[317,
[318,
[2,
'1']]]]]]]]]]]]]]]],
[8, ')']]]]],
[14, '+'],
[315,
[316,
[317,
[318, [2, '1']]]]]]]]]]]]]]]],
[8, ')']]]]]]]]]]]]]]]],
[4, ''],
[0, '']]
你可以通过这个简短的功能缓解疼痛:
def shallow(ast):
if not isinstance(ast, list): return ast
if len(ast) == 2: return shallow(ast[1])
return [ast[0]] + [shallow(a) for a in ast[1:]]
>>> pprint.pprint(shallow(parser.st2list(parser.expr('(((1+0)+1)+1)'))))
[258,
[318,
'(',
[314,
[318, '(', [314, [318, '(', [314, '1', '+', '0'], ')'], '+', '1'], ')'],
'+',
'1'],
')'],
'',
'']
这些数字来自Python模块symbol
和token
,您可以使用这些模块从数字到名称构建查找表:
map = dict(token.tok_name.items() + symbol.sym_name.items())
您甚至可以将此映射折叠到shallow()
函数中,以便您可以使用字符串而不是数字:
def shallow(ast):
if not isinstance(ast, list): return ast
if len(ast) == 2: return shallow(ast[1])
return [map[ast[0]]] + [shallow(a) for a in ast[1:]]
>>> pprint.pprint(shallow(parser.st2list(parser.expr('(((1+0)+1)+1)'))))
['eval_input',
['atom',
'(',
['arith_expr',
['atom',
'(',
['arith_expr',
['atom', '(', ['arith_expr', '1', '+', '0'], ')'],
'+',
'1'],
')'],
'+',
'1'],
')'],
'',
'']
答案 4 :(得分:5)
Stack是这项工作的最佳工具: -
import re
def matches(line, opendelim='(', closedelim=')'):
stack = []
for m in re.finditer(r'[{}{}]'.format(opendelim, closedelim), line):
pos = m.start()
if line[pos-1] == '\\':
# skip escape sequence
continue
c = line[pos]
if c == opendelim:
stack.append(pos+1)
elif c == closedelim:
if len(stack) > 0:
prevpos = stack.pop()
# print("matched", prevpos, pos, line[prevpos:pos])
yield (prevpos, pos, len(stack))
else:
# error
print("encountered extraneous closing quote at pos {}: '{}'".format(pos, line[pos:] ))
pass
if len(stack) > 0:
for pos in stack:
print("expecting closing quote to match open quote starting at: '{}'"
.format(line[pos-1:]))
在客户端代码中,由于函数是作为生成器函数编写的,只需使用for循环模式来展开匹配: -
line = '(((1+0)+1)+1)'
for openpos, closepos, level in matches(line):
print(line[openpos:closepos], level)
此测试代码在我的屏幕上产生以下内容,注意到打印输出中的第二个参数表示括号的深度。
1+0 2
(1+0)+1 1
((1+0)+1)+1 0
答案 5 :(得分:3)
来自链接的答案:
来自LilyPond convert-ly实用程序(由我自己编写/版权所有,所以我可以在这里展示):
def paren_matcher (n):
# poor man's matched paren scanning, gives up
# after n+1 levels. Matches any string with balanced
# parens inside; add the outer parens yourself if needed.
# Nongreedy.
return r"[^()]*?(?:\("*n+r"[^()]*?"+r"\)[^()]*?)*?"*n
convert-ly倾向于在其正则表达式中将其用作paren_matcher(25),这对于大多数应用程序来说可能是过度杀伤力。但后来它用它来匹配Scheme表达式。
是的,它在给定的限制之后崩溃了,但是将它插入正则表达式的能力仍然胜过&#34;正确的&#34;在可用性方面支持无限深度的替代方案。
答案 6 :(得分:1)
平衡对(例如括号)是正则表达式无法识别的语言示例。
以下是对数学原理的简要解释。
正则表达式是定义有限状态自动机(缩写为FSM)的一种方式。这种设备具有有限量的可能状态来存储信息。如何使用该状态不受特别限制,但它确实意味着它可以识别的绝对最大数量的不同位置。
例如,状态可用于计数,例如,不匹配的左括号。但是因为这种计数的状态量必须完全有限,所以给定的FSM可以计入最大值 n -1,其中 n 是数量如果 n 是10,那么FSM可以匹配的最大不匹配左括号数是10,直到它中断为止。由于完全可以再使用一个左括号,因此没有可能正确识别匹配括号的完整语言的FSM。
那又怎样?假设您选择了一个非常大的 n ?问题在于,作为描述FSM的一种方式,正则表达式基本上描述了从一个状态到另一个状态的所有转换。因为对于任何N,FSM将需要2个状态转换(一个用于匹配左括号,一个用于匹配右),正则表达式本身必须至少增长 n <的常数因子倍/ p>
相比之下,下一个更好的语言类(无上下文语法)可以以完全紧凑的方式解决这个问题。这是BNF中的一个例子
expression ::= `(` expression `)` expression
| nothing
答案 7 :(得分:0)
你应该编写一个适当的解析器来解析这样的表达式(例如使用pyparsing)。 正则表达式不适合编写正确的解析器。
答案 8 :(得分:0)
您可以使用regexp,但您需要自己进行递归。类似下面这样的诀窍(如果你只需要找到,正如你的问题所说,所有括在括号中的表达式):
import re
def scan(p, string):
found = p.findall(string)
for substring in found:
stripped = substring[1:-1]
found.extend(scan(p, stripped))
return found
p = re.compile('\(.+\)')
string = '(((1+0)+1)+1)'
all_found = scan(p, string)
print all_found
但是,此代码与“正确”括号不匹配。如果您需要这样做,那么使用专门的解析器会更好。
答案 9 :(得分:0)
这是一个针对你的问题的演示,虽然它很笨拙,但它有效
page {
& ion-item { // => page ion-item
&.item { // => page ion-item.item
color: red;
&[margin-bottom] { // => page ion-item.item[margin-bottom]
margin-bottom: 1em;
}
& ion-avatar { // => page ion-item.item ion-avatar
font-weight: bold;
}
}
}
}
答案 10 :(得分:0)
我的解决方案是:定义一个函数以提取最外括号内的内容,然后重复调用该函数,直到获得最内括号内的内容。
def get_string_inside_outermost_parentheses(text):
content_p = re.compile(r"(?<=\().*(?=\))")
r = content_p.search(text)
return r.group()
def get_string_inside_innermost_parentheses(text):
while '(' in text:
text = get_string_inside_outermost_parentheses(text)
return text
答案 11 :(得分:-1)
我相信这个功能可能适合你的需要,我把它快速地扔到一起,所以随意把它清理一下。在做巢时,很容易向后看,并从那里开始工作=]
def fn(string,endparens=False):
exp = []
idx = -1
for char in string:
if char == "(":
idx += 1
exp.append("")
elif char == ")":
idx -= 1
if idx != -1:
exp[idx] = "(" + exp[idx+1] + ")"
else:
exp[idx] += char
if endparens:
exp = ["("+val+")" for val in exp]
return exp
答案 12 :(得分:-1)
很多帖子都暗示对于嵌套大括号, REGEX不是这样做的方式。简单地计算一下: 例如,请参阅:Regular expression to detect semi-colon terminated C++ for & while loops
这是一个完整的python示例,用于遍历字符串并计算大括号:
# decided for nested braces to not use regex but brace-counting
import re, string
texta = r'''
nonexistent.\note{Richard Dawkins, \textit{Unweaving the Rainbow: Science, Delusion
and the Appetite for Wonder} (Boston: Houghton Mifflin Co., 1998), pp. 302, 304,
306-309.} more text and more.
This is a statistical fact, not a
guess.\note{Zheng Wu, \textit{Cohabitation: An Alternative Form
of Family Living} (Ontario, Canada: Oxford University Press,
2000), p. 149; \hbox{Judith} Treas and Deirdre Giesen, ``Title
and another title,''
\textit{Journal of Marriage and the Family}, February 2000,
p.\,51}
more and more text.capitalize
'''
pos = 0
foundpos = 0
openBr = 0 # count open braces
while foundpos <> -1:
openBr = 0
foundpos = string.find(texta, r'\note',pos)
# print 'foundpos',foundpos
pos = foundpos + 5
# print texta[pos]
result = ""
while foundpos > -1 and openBr >= 0:
pos = pos + 1
if texta[pos] == "{":
openBr = openBr + 1
if texta[pos] == "}":
openBr = openBr - 1
result = result + texta[pos]
result = result[:-1] # drop the last } found.
result = string.replace(result,'\n', ' ') # replace new line with space
print result