web-dev-qa-db-fra.com

Motif récursif en expression régulière

Ceci est très lié à Expression régulière pour correspondre aux crochets externes cependant, je veux spécifiquement savoir comment ou s'il est possible de le faire le motif récursif de regex ? Je n'ai pas encore trouvé un python utilisant cette stratégie alors pensez que cela devrait être une question utile !

J'ai vcertainsrevendicationsque les modèles récursifs peuvent être utilisés pour faire correspondre les parenthèses équilibrées, mais aucun exemple utilisant python regex package (Remarque: re ne prend pas en charge le motif récursif, vous devez utiliser regex).

Un revendication est que la syntaxe est b(?:m|(?R))*e où:

b est ce qui commence la construction, m est ce qui peut se produire au milieu de la construction, et e est ce qui peut se produire à la fin de la construction


Je veux extraire des correspondances pour les accolades externe comme suit:

"{1, {2, 3}} {4, 5}"
["1, {2, 3}", "4, 5"]  # desired

Notez qu'il est facile de faire de même pour les accolades intérieure:

re.findall(r"{([^{}]*)}", "{1, {2, 3}} {4, 5}")
['2, 3', '4, 5']

(Dans mon exemple, j'utilisais finditer (au-dessus des objets de correspondance), voir ici .)

J'avais donc espéré que ce qui suit, ou une variation, fonctionnerait:

regex.findall(r"{(:[^{}]*|?R)}", "{1, {2, 3}} {4, 5}")
regex.findall(r"({(:[^{}]*|?R)})", "{1, {2, 3}} {4, 5}")
regex.findall(r"({(:.*|(?R))*})", "{1, {2, 3}} {4, 5}")
regex.findall(r"({(:.*)|(?R)*})", "{1, {2, 3}} {4, 5}")
regex.findall(r"({(:[^{}])|(?R)})", "{1, {2, 3}} {4, 5}")

mais je suis sabordé par [] ou error: too much backtracking.

Est-il possible d'extraire des objets de correspondance pour la parenthèse externe en utilisant la récursivité de regex?


Évidemment, je risque d'être abattu avec:

Je veux souligner qu'il s'agit de comment utiliser le modèle récursif (qui, si ma compréhension est correcte, nous éloigne de l'analyse régulière du langage, donc peut en fait être possible!). Si cela est possible, cela devrait être une solution plus propre.

44
Andy Hayden

Le schéma est:

{((?>[^{}]+|(?R))*)}

Vous pouvez voir cela fonctionne pour votre exemple:

regex.findall("{((?>[^{}]+|(?R))*)}", "{1, {2, 3}} {4, 5}")
# ['1, {2, 3}', '4, 5']

Explication:

La partie m doit exclure les crochets. L'utilisation d'un groupe atomique est nécessaire si vous voulez en même temps autoriser un quantificateur pour [^{}] Et répéter le groupe sans problèmes de retour en arrière catastrophiques. Pour être plus clair, si le dernier crochet bouclé de fermeture est manquant, ce moteur d'expression régulière reviendra en arrière groupe atomique par groupe atomique au lieu de caractère par caractère. Pour revenir à ce point, vous pouvez rendre le quantificateur possessif comme ça: {((?>[^{}]+|(?R))*+)} (ou {((?:[^{}]+|(?R))*+)} puisque le groupe atomique n'est plus utile).

Le groupe atomique (?>....) Et le quantificateur possessif ?+, *+, ++ Sont les deux faces d'une même fonction. Cette fonctionnalité interdit au moteur d'expression régulière de revenir en arrière à l'intérieur du groupe de caractères qui devient un "atome" (quelque chose que vous ne pouvez pas diviser en plus petites parties).

Les exemples de base sont les deux modèles suivants qui échouent toujours pour la chaîne aaaaaaaaaab:

(?>a+)ab
a++ab

c'est:

regex.match("a++ab", "aaaaaaaaaab")
regex.match("(?>a+)ab", "aaaaaaaaaab")

Lorsque vous utilisez (?:a+) Ou a+, Le moteur d'expression régulière (par défaut) enregistre (en prévision) toutes les positions de retour en arrière pour tous les caractères. Mais lorsque vous utilisez un groupe atomique ou un quantificateur possessif, ces positions de retour en arrière ne sont plus enregistrées (sauf au début du groupe). Ainsi, lorsque le mécanisme de retour arrière se produit, le dernier caractère "a" ne peut pas être restitué. Seul l'ensemble du groupe peut être restitué.

[EDIT]: le modèle peut être écrit de manière plus efficace si vous utilisez un sous-modèle "non déroulé" pour décrire le contenu entre crochets:

{([^{}]*+(?:(?R)[^{}]*)*+)}
42

J'ai pu faire cela sans problème avec la syntaxe b(?:m|(?R))*e:

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

Démo


Je pense que la clé de ce que vous tentiez est que la répétition ne se passe pas sur m, mais sur l'ensemble (?:m|(?R)) groupe. C'est ce qui permet la récursivité avec le (?R) référence.

10
Sam