Semblables à nos discussions pour des astuces de golf spécifiques à une langue: quels sont les astuces générales pour raccourcir les expressions régulières?
Je peux voir trois utilisations de regex quand il s’agit de jouer au golf: le regex golf classique ("voici une liste qui devrait correspondre, et une liste qui devrait échouer"), en utilisant regex pour résoudre des problèmes de calcul et des expressions régulières utilisées plus gros code de golf. Ne hésitez pas à poster des conseils pour tout ou partie de ceux-ci. Si votre pourboire se limite à une ou plusieurs saveurs, veuillez indiquer ces saveurs en haut.
Comme d'habitude, veuillez vous en tenir à un conseil (ou à une famille de conseils très liés) par réponse, afin que les conseils les plus utiles puissent atteindre le sommet via le vote.
la source
Réponses:
Quand ne pas s'échapper
Ces règles s'appliquent à la plupart des saveurs, sinon à toutes:
]
n'a pas besoin de s'échapper lorsqu'il est sans pareil.{
et}
n'ont pas besoin de s'échapper quand ils ne font pas partie d'une répétition, par exemple des{a}
allumettes au{a}
sens littéral. Même si vous voulez associer quelque chose du genre{2}
, il vous suffit d'échapper à l'un d'eux, par exemple{2\}
.En cours de personnage:
]
n'a pas besoin de s'échapper lorsqu'il s'agit du premier caractère d'un jeu de caractères, par exemple,[]abc]
correspond à l'un des]abc
, ou lorsqu'il s'agit du deuxième caractère après un^
, par exemple[^]]
correspond à tout sauf]
. (Exception notable: saveur ECMAScript!)[
n'a pas besoin de s'échapper du tout. Avec le conseil ci-dessus, cela signifie que vous pouvez associer les deux supports à la classe de caractères horriblement contre-intuitive[][]
.^
n'a pas besoin de s'échapper lorsqu'il ne s'agit pas du premier caractère d'un jeu de caractères, par exemple[ab^c]
.-
n'a pas besoin d'échapper s'il s'agit du premier (deuxième après un^
) ou du dernier caractère d'un jeu de caractères, par exemple[-abc]
,[^-abc]
ou[abc-]
.Aucun autre caractère n'a besoin de s'échapper à l'intérieur d'une classe de caractère, même s'il s'agit de méta-caractères en dehors des classes de caractères (à l'exception de la barre oblique inverse
\
elle-même).En outre, dans certains goûts
^
et$
sont appariés littéralement quand ils ne sont pas respectivement au début ou à la fin de la regex.(Merci à @ MartinBüttner d'avoir fourni quelques détails)
la source
[.]
). Echapper normalement sauverait 1 octet dans ce cas\.
[
doit être échappé en Java. Pas sûr de l'ICU (utilisé dans Android et iOS) ou .NET, cependant.Une expression régulière simple pour faire correspondre tous les caractères imprimables de la table ASCII .
la source
Connaissez vos goûts regex
Un nombre surprenant de personnes pensent que les expressions régulières sont essentiellement agnostiques. Cependant, il existe des différences assez importantes entre les saveurs, et en particulier pour le golf de code, il est bon de connaître quelques-unes d'entre elles, ainsi que leurs caractéristiques intéressantes, afin que vous puissiez choisir le meilleur pour chaque tâche. Voici un aperçu de plusieurs saveurs importantes et de ce qui les distingue des autres. (Cette liste ne peut pas être vraiment complète, mais laissez-moi savoir si j'ai raté quelque chose de vraiment flagrant.)
Perl et PCRE
Je les jette dans un seul récipient, car je ne connais pas très bien la saveur Perl, qui est la plupart du temps équivalente (PCRE est après tout des expressions régulières compatibles Perl). L'avantage principal de la version Perl est que vous pouvez appeler du code Perl depuis l'intérieur de la regex et de la substitution.
(?(group)yes|no)
.\l
,\u
,\L
et\U
.\G
d'ancrer un match à la fin du match précédent.\K
réinitialiser le début du match\Q...\E
pour échapper à de plus longues séries de caractères. Utile lorsque vous essayez de faire correspondre une chaîne contenant de nombreux méta-caractères..NET
C'est probablement la saveur la plus puissante, avec seulement très peu d'inconvénients.
[\w-[aeiou]]
\d
sont compatibles Unicode.Une lacune importante en termes de golf est qu’elle ne prend pas en charge les quantificateurs possessifs comme certains autres parfums. Au lieu de cela,
.?+
vous devrez écrire(?>.?)
.Java
.*
delà du début de la chaîne, d'où vous pouvez maintenant commencer une recherche, comme(?<=(?=lookahead).*)
.\Q...\E
comme dans Perl / PCRE.Rubis
Dans les versions récentes, cette version est aussi puissante que PCRE, y compris la prise en charge des appels de sous-routines. Comme Java, il prend également en charge l'union et l'intersection de classes de caractères. Une caractéristique spéciale est la classe de caractères intégrée pour les chiffres hexadécimaux:
\h
(et le négatif\H
).La fonctionnalité la plus utile pour jouer au golf est cependant la façon dont Ruby gère les quantificateurs. Plus particulièrement, il est possible d'imbriquer des quantificateurs sans parenthèses.
.{5,7}+
fonctionne et fait ainsi.{3}?
. En outre, contrairement à la plupart des autres versions, si la limite inférieure d’un quantificateur est définie,0
elle peut être omise, par exemple.{,5}
équivalente à.{0,5}
.En ce qui concerne les sous - routines, la différence majeure entre les sous - routines de PCRE et les sous - routines de Ruby, est que la syntaxe de Ruby est un octet plus
(?n)
vs\g<n>
, mais les sous - routines de Ruby peut être utilisé pour la capture, alors que PCRE remet à zéro la capture après une finition de sous - programme.Enfin, Ruby a une sémantique différente pour les modificateurs liés aux lignes de la plupart des autres versions. Le modificateur généralement appelé
m
dans d'autres versions est toujours activé dans Ruby. Donc^
, faites$
toujours correspondre le début et la fin d'une ligne, pas seulement le début et la fin de la chaîne. Cela peut vous faire économiser un octet si vous avez besoin de ce comportement, mais cela vous coûtera des octets supplémentaires si vous ne le faites pas, car vous devrez remplacer^
et$
par\A
et\z
respectivement. En plus de cela, le modificateur généralement appelés
(qui fait.
correspondre les sauts de ligne) est appelém
en Ruby. Cela n'affecte pas le nombre d'octets, mais il faut le garder à l'esprit pour éviter toute confusion.Python
Python a une saveur solide, mais je ne connais pas de fonctionnalités particulièrement utiles que vous ne trouverez nulle part ailleurs.
Cependant , il existe une variante qui vise à remplacer le
re
module à un moment donné et qui contient de nombreuses fonctionnalités intéressantes. Outre la prise en charge de la récursivité, des recherches de longueur variable et des opérateurs de combinaison de classes de caractères, il offre également la caractéristique unique de la correspondance floue . En substance, vous pouvez spécifier un certain nombre d'erreurs (insertions, suppressions, substitutions) autorisées. Le moteur vous donnera également des correspondances approximatives.ECMAScript
La saveur ECMAScript est très limitée, et donc rarement très utile pour le golf. La seule chose qui lui va, c'est la classe de caractères vides annulée
[^]
pour correspondre à n'importe quel caractère, ainsi que la classe de caractères vides qui échoue inconditionnellement[]
(par opposition à l'habituel(?!)
). Malheureusement, la saveur n'a aucune caractéristique qui rend ce dernier utile pour les problèmes normaux.Lua
Lua a sa propre saveur assez unique, qui est assez limitée (par exemple, vous ne pouvez même pas quantifier les groupes) mais qui vient avec une poignée de fonctionnalités utiles et intéressantes.
%b
cela prend en charge une syntaxe très compacte pour faire correspondre les chaînes équilibrées. Par exemple,%b()
correspond à a(
, puis à tout, jusqu'à une correspondance)
(en sautant correctement les paires appariées internes).(
et)
peut être deux personnages ici.Renforcer
La saveur regex de Boost est essentiellement celle de Perl. Cependant, il a quelques nouvelles fonctionnalités intéressantes pour la substitution de regex, y compris les modifications de casse et les conditions . Ce dernier est propre à Boost, à ma connaissance.
la source
.?+
équivalent à.*
?Connaissez vos classes de personnage
La plupart des saveurs de regex ont des classes de caractères prédéfinies. Par exemple,
\d
correspond à un chiffre décimal, inférieur de trois octets à[0-9]
. Oui, ils peuvent être légèrement différents, car ils\d
peuvent également correspondre aux chiffres Unicode dans certaines versions, mais pour la plupart des problèmes, cela ne fera aucune différence.Voici quelques classes de caractères trouvées dans la plupart des saveurs de regex:
De plus, nous avons aussi:
qui sont des versions annulées de ce qui précède.
Assurez-vous de vérifier votre goût pour toutes les classes de caractères supplémentaires qu'il pourrait avoir. Par exemple, PCRE a
\R
pour les nouvelles lignes et Lua a même des classes telles que les minuscules et les majuscules.(Merci à @HamZa et @ MartinBüttner pour les signaler)
la source
\R
pour les nouvelles lignes dans PCRE.Ne vous embêtez pas avec des groupes sans capture (sauf si ...)
Cette astuce s'applique à (au moins) toutes les saveurs populaires inspirées de Perl.
Cela peut sembler évident, mais (lorsque vous ne jouez pas au golf), il est recommandé d’utiliser des groupes de non-capture
(?:...)
autant que possible.?:
Cependant, ces deux personnages supplémentaires sont inutiles lors de la pratique du golf. Il vous suffit donc d’utiliser des groupes de capture, même si vous ne les référez pas.Il existe cependant une exception (rare): si vous parvenez
10
à au moins trois fois à un groupe de référence arrière , vous pouvez réellement sauvegarder des octets en transformant un groupe précédent en un groupe sans capture, de sorte que tous ces\10
s deviennent des\9
s. (Des astuces similaires s'appliquent si vous utilisez le groupe11
au moins 5 fois, etc.).la source
$9
place$10
ou$11
une fois enregistre un octet. La conversion$10
en$9
nécessite un?:
, qui est de deux octets. Vous aurez donc besoin de trois$10
secondes pour enregistrer quelque chose. La conversion$11
en$9
deux requiert?:
quatre octets, vous aurez donc besoin de cinq$11
secondes pour enregistrer quelque chose (ou cinq$10
et$11
combinés).Récursion pour la réutilisation de modèles
Une poignée de saveurs supportent la récursivité ( à ma connaissance , Perl, PCRE et Ruby). Même lorsque vous n'essayez pas de résoudre des problèmes récursifs, cette fonctionnalité peut économiser beaucoup d'octets dans des modèles plus compliqués. Il n'est pas nécessaire d'appeler un autre groupe (nommé ou numéroté) à l'intérieur même de ce groupe. Si vous avez un certain motif qui apparaît plusieurs fois dans votre expression rationnelle, il vous suffit de le regrouper et de vous y référer en dehors de ce groupe. Ce n'est pas différent d'un appel de sous-routine dans les langages de programmation normaux. Donc au lieu de
en Perl / PCRE, vous pouvez faire:
ou en rubis:
à condition qu'il s'agisse du premier groupe (vous pouvez bien entendu utiliser n'importe quel numéro dans l'appel récursif).
Notez que ce n’est pas la même chose qu’un backreference (
\1
). Les références arrière correspondent exactement à la même chaîne que la dernière fois pour le groupe. Ces appels de sous-programmes évaluent à nouveau le motif. Par exemple,someComplexPatternHere
prenons une longue classe de caractères:Cela correspond à quelque chose comme
Notez que vous ne pouvez pas utiliser de références arrières ici tout en préservant le comportement. Une référence arrière échouerait sur la chaîne ci-dessus, car
B
et0
et!
ne sont pas identiques. Cependant, avec les appels de sous-programmes, le modèle est en réalité réévalué. Le modèle ci-dessus est complètement équivalent àCapture d'appels de sous-programmes
Une mise en garde pour Perl et PCRE: si le groupe
1
dans les exemples ci-dessus contient d'autres groupes, les appels de sous-programmes ne se souviendront pas de leurs captures. Considérons cet exemple:Cela ne correspondra pas
car après le retour des appels de sous-routines, la nouvelle capture du groupe
2
est ignorée. Au lieu de cela, ce modèle correspond à cette chaîne:Ceci est différent de Ruby, où les appels de sous - programme font garder leurs captures, de sorte que le Ruby regex équivalent
(\w(\d):)\2 \g<1>\2 \g<1>\2
correspondrait à la première des exemples ci - dessus.la source
\1
pour Javascript. Et PHP aussi (je suppose).(..)\1
correspondraitabab
mais échoueraitabba
alors que(..)(?1)
correspondrait à ce dernier. C'est en fait un appel de sous-programme dans le sens où l'expression est appliquée à nouveau, au lieu de correspondre littéralement à ce à quoi elle correspondait la dernière fois.(?=a.b.c)(.[0_B!$]){3}d
Causer un match pour échouer
Lorsque vous utilisez regex pour résoudre des problèmes de calcul ou pour faire correspondre des langages extrêmement non normaux, il est parfois nécessaire de faire échouer une branche du motif, quel que soit votre emplacement dans la chaîne. L’approche naïve consiste à utiliser un regard négatif négatif:
Le contenu (le motif vide) correspond toujours, de sorte que la perspective négative échoue toujours. Mais le plus souvent, il existe une option beaucoup plus simple: il suffit d'utiliser un caractère dont vous savez qu'il n'apparaîtra jamais dans l'entrée. Par exemple, si vous savez que votre entrée sera toujours composée uniquement de chiffres, vous pouvez simplement utiliser
ou tout autre caractère non numérique, non méta, susceptible de provoquer un échec.
Même si votre entrée peut potentiellement contenir des sous-chaînes, il existe des méthodes plus courtes que
(?!)
. Toute saveur permettant aux ancres d'apparaître dans un motif, par opposition à la fin, pourrait utiliser l'une des solutions à 2 caractères suivantes:A noter cependant que certaines saveurs traiteront
^
et$
comme des caractères dans ces positions, parce qu'ils ne font pas réellement évidemment sens comme points d' ancrage.Dans la version ECMAScript, il existe également la solution plutôt élégante à deux caractères
Il s'agit d'une classe de caractères vide, qui essaie de s'assurer que les prochains caractères est l'un de ceux de la classe - mais il n'y a pas de caractères dans la classe, donc cela échoue toujours. Notez que cela ne fonctionnera sous aucune autre forme, car les classes de caractères ne peuvent généralement pas être vides.
la source
Optimisez vous OU
Chaque fois que vous avez 3 alternatives ou plus dans votre RegEx:
Vérifiez s'il y a un début commun:
Et peut-être même une fin commune?
Remarque: 3 n'est que le début et prendrait en compte la même longueur, 4+ ferait une différence
Mais que se passe-t-il s'ils n'ont pas tous un préfixe commun? (les espaces ne sont ajoutés que par souci de clarté)
Regroupez-les, tant que la règle des 3 ans et plus est logique:
Ou même généraliser si l'entropie satisfait votre cas d'utilisation:
^ dans ce cas, nous sommes sûrs que nous ne recevons
clue
nicrown
slack
Ryan
Cela "selon certains tests" améliore également les performances, car il fournit un point d' ancrage pour commencer.
la source
aqua|aquamarine
→aqua(|marine)
ouaqua(marine)?
.Celui-ci est assez simple, mais mérite d'être signalé:
Si vous vous trouvez répéter la classe de caractères que
[a-zA-Z]
vous pouvez probablement utiliser[a-z]
et append lei
(Case- i modificateur nsensitive) à votre regex.Par exemple, en Ruby, les deux expressions rationnelles suivantes sont équivalentes:
/[a-zA-Z]+\d{3}[a-zA-Z]+/
/[a-z]+\d{3}[a-z]/i
- 7 octets plus courtD'ailleurs, les autres modificateurs peuvent aussi raccourcir votre longueur totale. Au lieu de faire ceci:
/(.|\n)/
qui correspond à TOUT caractère (car le point ne correspond pas à la nouvelle ligne), utilisez le modificateur s ingle-line
s
, qui fait correspondre les points à la nouvelle ligne././s
- 3 octets plus courtDans Ruby, il existe une tonne de classes de caractères intégrées pour regex. Voir cette page et rechercher "Propriétés du personnage".
Un bon exemple est le "symbole monétaire". Selon Wikipedia, il existe une tonne de symboles monétaires possibles, et les placer dans une classe de caractères coûterait très cher (
[$฿¢₡Ð₫€.....
]) alors que vous pouvez les faire correspondre à 6 octets:\p{Sc}
la source
s
modificateur n'est pas pris en charge. :( Mais là, vous pouvez utiliser l'/[^]/
astuce propriétaire de JavaScript .(.|\n)
cela ne fonctionne même pas dans certaines saveurs, car.
souvent aussi ne correspond à aucun autre type de séparateur de ligne. Cependant, la manière habituelle de faire cela (sanss
) est d’utiliser[\s\S]
les mêmes octets que(.|\n)
.Un analyseur de langage simple
Vous pouvez construire un analyseur syntaxique très simple avec un RE tel que
\d+|\w+|".*?"|\n|\S
. Les jetons que vous devez associer sont séparés par le caractère RE 'ou'.Chaque fois que le moteur RE essaie de faire correspondre la position actuelle dans le texte, il essaiera le premier motif, puis le second, etc. S'il échoue (sur un caractère d'espacement ici par exemple), il continue et essaie à nouveau les correspondances. . L'ordre est important. Si nous plaçions le
\S
terme avant le\d+
terme,\S
il apparaitrait en premier sur n'importe quel caractère non spatial qui romprait notre analyseur.Le
".*?"
correcteur de chaînes utilise un modificateur non-glouton, nous ne faisons donc correspondre qu'une chaîne à la fois. Si votre RE n'a pas de fonctions non gloutonnes, vous pouvez utiliser"[^"]*"
ce qui est équivalent.Exemple Python:
Exemple Python Golfé:
Vous pouvez ajuster les modèles et leur ordre en fonction de la langue à associer. Cette technique fonctionne bien pour les expressions JSON, HTML de base et numériques. Il a été utilisé avec succès à plusieurs reprises avec Python 2, mais devrait être suffisamment général pour fonctionner dans d'autres environnements.
la source
\K
au lieu de lookbehind positifPCRE et Perl prennent en charge la séquence d'échappement
\K
, ce qui réinitialise le début du match. Celaab\Kcd
nécessitera que votre chaîne d'entrée contienne,abcd
mais la correspondance rapportée sera seulementcd
.Si vous utilisez une recherche positive au début de votre modèle (ce qui est probablement l'endroit le plus probable), dans la plupart des cas, vous pouvez utiliser à la
\K
place et économiser 3 octets:Ceci est équivalent dans la plupart des cas, mais pas entièrement. Les différences apportent à la fois des avantages et des inconvénients:
(?<=ab*)
. Mais avec\K
vous pouvez mettre n'importe quel motif devant! Alors çaab*\K
marche. Cela rend cette technique beaucoup plus puissante dans les cas où elle est applicable.\K
cette partie de la regex, vous faites comme si de rien n'était.Inconvénient: Comme vous le savez probablement, plusieurs correspondances d'une expression rationnelle ne peuvent pas se chevaucher. Les méthodes de recherche sont souvent utilisées pour contourner partiellement cette limitation, car elles permettent de valider une partie de la chaîne déjà utilisée par une correspondance antérieure. Donc, si vous voulez faire correspondre tous les caractères qui suivent,
ab
vous pouvez utiliser(?<=ab).
. Compte tenu de l'entréecela correspondrait à la seconde
a
et lac
. Cela ne peut pas être reproduit avec\K
. Si vous utilisiezab\K.
, vous n'obtiendrez que le premier match, car à présent, ceab
n'est pas un lookaround.la source
\K
séquence d'échappement dans une assertion positive, le début signalé d'une correspondance réussie peut être supérieur à la fin de la correspondance.ababc
, il n'y a aucun moyen de faire correspondre à la fois la secondea
et lac
avec\K
. Vous aurez seulement un match.\G
.
le dernier match a été una
.Faire correspondre n'importe quel personnage
La variante ECMAScript manque des
s
modificateurs, ce qui fait.
correspondre tout caractère (y compris les nouvelles lignes). Cela signifie qu'il n'y a pas de solution à un seul caractère pour faire correspondre des caractères complètement arbitraires. La solution standard dans d'autres versions (quand on ne veut pas utilisers
pour une raison quelconque) est[\s\S]
. Cependant, ECMAScript est la seule saveur (à ma connaissance) qui prend en charge les classes de caractères vides, et a donc une alternative beaucoup plus courte:[^]
. Il s'agit d'une classe de caractères vides refusée - c'est-à-dire qu'elle correspond à n'importe quel caractère.Même pour d’autres saveurs, nous pouvons tirer des enseignements de cette technique: si nous ne voulons pas utiliser
s
(par exemple, parce que nous avons toujours besoin de la signification habituelle de.
ailleurs), il peut toujours exister un moyen plus court de faire correspondre les caractères de nouvelle ligne et les caractères imprimables, à condition qu'il y ait un caractère que nous savons n'apparaît pas dans l'entrée. Disons que nous traitons des nombres délimités par des nouvelles lignes. Ensuite, nous pouvons associer n'importe quel caractère[^!]
, car nous savons que!
cela ne fera jamais partie de la chaîne. Cela enregistre deux octets sur le naïf[\s\S]
ou[\d\n]
.la source
\N
signifie exactement ce que.
signifie en dehors du/s
mode, sauf que ce mode n'est pas affecté par un mode.Utiliser des groupes atomiques et des quantificateurs possessifs
J'ai trouvé des groupes atomiques (
(?>...)
) et quantificateurs possessifs (?+
,*+
,++
,{m,n}+
) parfois très utiles pour jouer au golf. Il correspond à une chaîne et interdit les retours en arrière plus tard. Ainsi, il ne fera correspondre que la première chaîne pouvant être lue que le moteur de regex trouve.Par exemple: Pour faire correspondre une chaîne avec un nombre impair de
a
'au début, qui n'est pas suivi de plusieursa
', vous pouvez utiliser:Cela vous permet d'utiliser des éléments comme
.*
librement, et s'il y a une correspondance évidente, il n'y aura pas d'autre possibilité de faire correspondre trop ou trop peu de caractères, ce qui pourrait briser votre modèle.Dans regex .NET (qui n'a pas de quantificateurs possessifs), vous pouvez l'utiliser pour faire apparaître le plus grand multiple du groupe 1 sur 3 (avec un maximum de 30) (sans très bien jouer au golf):
la source
Oublier un groupe capturé après une sous-expression (PCRE)
Pour cette regex:
Si vous souhaitez effacer le \ 2 après le groupe 1, vous pouvez utiliser la récursivité:
Il s'agira
aa
alors que le précédent ne le sera pas. Parfois, vous pouvez également utiliser??
ou même?
à la place de{0}
.Cela peut être utile si vous utilisez beaucoup de récursions et que certaines des références arrières ou des groupes conditionnels apparaissent à différents endroits de votre expression régulière.
Notez également que les groupes atomiques sont supposés être des récurrences dans PCRE. Donc, cela ne correspondra pas à une seule lettre
a
:Je ne l'ai pas encore essayé dans d'autres saveurs.
Pour les têtes d’attente, vous pouvez également utiliser des doubles négatifs à cette fin:
la source
Expressions facultatives
Il est parfois utile de se rappeler que
est la plupart du temps identique à
Il y a cependant une petite différence: dans le premier cas, le groupe capture
abc
ou ne capture pas du tout. Ce dernier cas ferait échouer une référence arrière inconditionnellement. Dans la deuxième expression, le groupe captureraabc
ou une chaîne vide, où le dernier cas fera une correspondance de référence arrière sans condition. Pour émuler ce dernier comportement avec?
vous, vous devez tout entourer d'un autre groupe, ce qui coûterait deux octets:La version using
|
est également utile lorsque vous souhaitez néanmoins insérer l'expression dans une autre forme de groupe sans vous soucier de la capture:Enfin, cette astuce peut également être appliquée à ungreedy
?
où il enregistre un octet même sous sa forme brute (et par conséquent 3 octets lorsqu'il est combiné avec d'autres formes de groupes):la source
Plusieurs lookaheads qui correspondent toujours (.NET)
Si vous avez 3 constructions lookahead ou plus qui correspondent toujours (pour capturer des sous-expressions), ou s'il existe un quantificateur sur un lookahead suivi de quelque chose d'autre, elles doivent donc être dans un groupe pas nécessairement capturé:
Ceux-ci sont plus courts:
où
a
ne devrait pas être le nom d'un groupe capturé. Vous ne pouvez pas utiliser|
pour signifier la chose habituelleb
etc
sans ajouter une autre paire de parenthèses.Malheureusement, les groupes d'équilibrage dans les conditionnels semblaient difficiles, ce qui le rendait souvent inutile.
la source