Comme le titre l'indique, voici un exemple d'entrée:
(outer
(center
(inner)
(inner)
center)
ouer)
(outer
(inner)
ouer)
(outer
ouer)
Bien entendu, les chaînes correspondantes seront traitées par récursivité.
Je veux que la première récursion corresponde:
[
(outer
(center
(inner)
(inner)
center)
ouer),
(outer
(inner)
ouer),
(outer
ouer)]
Et il est inutile de dire que les processus après sont ...
De nombreuses implémentations regex ne vous permettront pas de faire correspondre une quantité arbitraire d'imbrication. Cependant, Perl, PHP et .NET prennent en charge les modèles récursifs.
Une démo en 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");
}
qui va imprimer:
----------.__ (extérieur (centre (intérieur) (intérieur) centre) ouer) ----- ----- (extérieur (intérieur) ouer) ---------- (extérieur ouer)
Ou l'équivalent PHP:
$text = '(outer
(center
(inner)
(inner)
center)
ouer)
(outer
(inner)
ouer)
(outer
ouer)';
preg_match_all('/(\(([^()]|(?R))*\))/', $text, $matches);
print_r($matches);
qui produit:
Tableau ( [0] => Tableau ( [0] => (extérieur (Centre (Intérieur) (Intérieur) Centre.) ouer) [1] => (extérieur (intérieur) ou.) [2] => (extérieur ou.) ) ....
Une explication:
(# début du groupe 1 \(# correspond à un littéral '(' (# groupe 2 [^ ()] # tout caractère autre que '(' et ')' | # OU (? R) # correspond récursivement au motif entier ) * # Groupe d'extrémité 2 et répétez zéro ou plusieurs fois \) # Correspond à un littéral ')'. 1
Remarquez le commentaire de Goozak:
Un meilleur modèle pourrait être
\(((?>[^()]+)|(?R))*\)
(from PHP: Patterns récursifs ). Pour mes données, le modèle de Bart plantait PHP lorsqu'il rencontrait une (chaîne longue) sans imbrication. Ce modèle a traversé toutes mes données sans problème.
N'utilisez pas de regex.
Au lieu de cela, une simple fonction récursive suffira:
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
J'ai trouvé cette expression rationnelle simple qui extrait tous les groupes équilibrés imbriqués à l'aide de la récursivité, bien que la solution résultante ne soit pas aussi simple que vous le souhaiteriez:
Modèle de regex: (1(?:\1??[^1]*?2))+
Exemple d'entrée: 1ab1cd1ef2221ab1cd1ef222
Pour plus de simplicité, je mets 1
pour ouvert et 2
pour crochet fermé. Les caractères alphabétiques représentent des données internes ... Je vais réécrire les entrées pour qu'elles soient faciles à expliquer.
1 ab 1 cd 1ef2 2 2 1 ab 1 cd 1ef2 2 2 |_1| |______2__| |_____________3_____|
Lors de la première itération, l'expression rationnelle correspond au sous-groupe le plus interne, 1ef2
, du premier groupe apparenté, 1ab1cd1ef222
. Si nous nous en souvenons et que c'est sa position, et retirons ce groupe, il resterait 1ab1cd22
. Si nous continuons avec regex, il renverrait 1cd2
et enfin 1ab2
. Ensuite, il continuera à analyser le deuxième groupe frère de la même manière.
Comme nous le voyons dans cet exemple, regex extraira correctement les sous-chaînes telles qu'elles apparaissent dans la hiérarchie définie entre crochets. La position de la sous-chaîne en particulier dans la hiérarchie sera déterminée lors de la deuxième itération. Si sa position dans la chaîne se situe entre la sous-chaîne de la deuxième itération, il s’agit d’un nœud enfant, sinon d’un nœud frère.
De notre exemple:
1ab1cd1ef222 1ab1cd1ef222
, correspondance d'itération 1ef2
, avec index 6
,
1ab1cd22 1ab1cd1ef222
, correspondance d'itération 1cd2
, avec index 3
, se terminant par 6
. Étant donné que 3
<6
<= 6
, la première sous-chaîne est l'enfant de la deuxième sous-chaîne.
1ab2 1ab1cd1ef222
, correspondance d'itération 1ab2
, avec index 0
, se terminant par 3
. Étant donné que 0
<3
<= 3
, la première sous-chaîne est l'enfant de la deuxième sous-chaîne.
1ab1cd1ef222
, correspondance d'itération 1ef2
, avec index 6
, Parce que ce n'est pas 3
<0
<= 6
, c'est une branche d'un autre frère, etc ...
Nous devons itérer et supprimer tous les frères et sœurs avant de pouvoir passer au parent. Ainsi, nous devons nous souvenir de tous ces frères et sœurs dans l'ordre dans lequel ils apparaissent dans l'itération.
Code Delphi Pascal basé sur l'affichage ci-dessus de nneonneo :
Vous avez besoin d'un formulaire avec un bouton, nommé btnRun. Dans le code source, remplacez "arnolduss" par votre nom dans le dossier DownLoads. Notez le niveau de pile dans la sortie créée par ParseList. Il est évident que les crochets du même type doivent s’ouvrir et se fermer au même niveau de pile. Vous pourrez maintenant extraire les soi-disant groupes par niveau de pile.
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.