正如标题所说,这是一个示例输入:
(outer
(center
(inner)
(inner)
center)
ouer)
(outer
(inner)
ouer)
(outer
ouer)
当然,匹配的字符串将通过递归处理。
我希望第一次递归匹配:
[
(outer
(center
(inner)
(inner)
center)
ouer),
(outer
(inner)
ouer),
(outer
ouer)]
后续流程不用说......
答案 0 :(得分:29)
许多正则表达式实现不允许您匹配任意数量的嵌套。但是,Perl,PHP和.NET支持递归模式。
Perl中的演示:
#!/usr/bin/perl -w
my $text = '(outer
(center
(inner)
(inner)
center)
ouer)
(outer
(inner)
ouer)
(outer
ouer)';
while($text =~ /(\(([^()]|(?R))*\))/g) {
print("----------\n$1\n");
}
将打印:
---------- (outer (center (inner) (inner) center) ouer) ---------- (outer (inner) ouer) ---------- (outer ouer)
或者,PHP等价物:
$text = '(outer
(center
(inner)
(inner)
center)
ouer)
(outer
(inner)
ouer)
(outer
ouer)';
preg_match_all('/(\(([^()]|(?R))*\))/', $text, $matches);
print_r($matches);
产生:
Array ( [0] => Array ( [0] => (outer (center (inner) (inner) center) ouer) [1] => (outer (inner) ouer) [2] => (outer ouer) ) ...
解释:
( # start group 1 \( # match a literal '(' ( # group 2 [^()] # any char other than '(' and ')' | # OR (?R) # recursively match the entir pattern )* # end group 2 and repeat zero or more times \) # match a literal ')' ) # end group 1
注意@ Goozak的评论:
更好的模式可能是
\(((?>[^()]+)|(?R))*\)
(来自PHP:Recursive patterns)。对于我的数据,Bart的模式在遇到没有嵌套的(长字符串)时崩溃了PHP。这个模式没有问题地遍历了我的所有数据。
答案 1 :(得分:21)
不要使用正则表达式。
相反,一个简单的递归函数就足够了:
def recursive_bracket_parser(s, i):
while i < len(s):
if s[i] == '(':
i = recursive_bracket_parser(s, i+1)
elif s[i] == ')':
return i+1
else:
# process whatever is at s[i]
i += 1
return i
答案 2 :(得分:8)
我发现这个简单的正则表达式使用递归提取所有嵌套的平衡组,尽管得到的解决方案并不像你期望的那样简单:
正则表达式模式:(1(?:\1??[^1]*?2))+
示例输入:1ab1cd1ef2221ab1cd1ef222
为简单起见,我将1
设置为open,2
设置为封闭括号。 Alpha字符代表一些内部数据。
我会重写输入,以便于解释。
1 ab 1 cd 1ef2 2 2 1 ab 1 cd 1ef2 2 2 |_1| |______2__| |_____________3_____|
在第一次迭代中,regex将匹配第一个兄弟组1ef2
中最内层的子组1ab1cd1ef222
。如果我们记住它及其位置,并删除此组,则会保留1ab1cd22
。如果我们继续使用正则表达式,它将返回1cd2
,最后返回1ab2
。然后,它将继续以相同的方式解析第二个兄弟组。
正如我们从这个例子中看到的,正则表达式将正确提取子括号,因为它们出现在由括号定义的层次结构中。层次结构中特别子串的位置将在第二次迭代中确定,如果它在string中的位置在第二次迭代的子串之间,那么它是子节点,否则它是一个兄弟节点。
从我们的例子中可以看出:
1ab1cd1ef222 1ab1cd1ef222
,迭代匹配1ef2
,索引为6
,
1ab1cd22 1ab1cd1ef222
,迭代匹配1cd2
,索引3
,以6
结尾。
因为3
&lt; 6
&lt; = 6
,第一个子字符串是第二个子字符串的子字符。
1ab2 1ab1cd1ef222
,迭代匹配1ab2
,索引0
,以3
结尾。
因为0
&lt; 3
&lt; = 3
,第一个子字符串是第二个子字符串的子字符。
1ab1cd1ef222
,迭代匹配1ef2
,索引为6
,
因为它不是3
&lt; 0
&lt; = 6
,它来自另一个兄弟姐妹的分支......
我们必须迭代并删除所有兄弟姐妹才能转移到父母。因此,我们必须按照它们在迭代中出现的顺序记住所有兄弟姐妹。
答案 3 :(得分:0)
基于nneonneo上方发布的Delphi Pascal代码:
您需要一个带有按钮的表单,名为btnRun。在源代码中,替换&#34; arnolduss&#34;在DownLoads文件夹中显示您的姓名。请注意ParseList创建的输出中的堆栈级别。显然,相同类型的括号必须在同一堆栈级别上打开和关闭。您现在可以在每个堆栈级别提取所谓的组。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
Type TCharPos = record
Level: integer;
Pos: integer;
Char: string;
end;
type
TForm1 = class(TForm)
btnRun: TButton;
procedure btnRunClick(Sender: TObject);
private
{ Private declarations }
procedure FormulaFunctionParser(var CharPos: array of TCharPos;
const formula, LBracket, RBracket: string; var itr, iChar,
iLevel: integer; var ParseList: TStringList);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnRunClick(Sender: TObject);
var
formula: string;
CharPos: array of TCharPos;
itr, iChar, iLevel: integer;
ParseList: TStringList;
begin
screen.Cursor := crHourGlass;
itr := 0;
iChar := 1;
iLevel := 0;
ParseList := TStringList.Create;
try
formula := Trim('add(mul(a,add(b,c)),d) + e - sub(f,g)');
ParseList.Add(formula);
ParseList.Add('1234567890123456789012345678901234567890');
SetLength(CharPos, Length(formula));
FormulaFunctionParser(CharPos[0], formula, '(', ')', itr, iChar, iLevel, ParseList);
finally
ParseList.SaveToFile('C:\Users\arnolduss\Downloads\ParseList.txt');
FreeAndNil(ParseList);
screen.Cursor := crDefault;
end;
end;
//Based on code from nneonneo in: https://stackoverflow.com/questions/14952113/how-can-i-match-nested-brackets-using-regex
procedure TForm1.FormulaFunctionParser(var CharPos: array of TCharPos;
const formula, LBracket, RBracket: string; var itr, iChar,
iLevel: integer; var ParseList: TStringList);
procedure UpdateCharPos;
begin
CharPos[itr-1].Level := iLevel;
CharPos[itr-1].Pos := itr;
CharPos[itr-1].Char := formula[iChar];
ParseList.Add(IntToStr(iLevel) + ' ' + intToStr(iChar) + ' = ' + formula[iChar]);
end;
begin
while iChar <= length(formula) do
begin
inc(itr);
if formula[iChar] = LBracket then
begin
inc(iLevel);
UpdateCharPos;
inc(iChar);
FormulaFunctionParser(CharPos, formula, LBracket, RBracket, itr, iChar, iLevel, ParseList);
end
else
if formula[iChar] = RBracket then
begin
UpdateCharPos;
dec(iLevel);
inc(iChar);
end
else
begin
UpdateCharPos;
inc(iChar);
end;
end;
end;
end.