Je suis tombé sur un fait surprenant (pour moi).
console.log("asdf".replace(/.*/g, "x"));
Pourquoi deux remplacements? Il semble que toute chaîne non vide sans retour à la ligne produira exactement deux remplacements pour ce modèle. En utilisant une fonction de remplacement, je peux voir que le premier remplacement est pour la chaîne entière et le second pour une chaîne vide.
javascript
regex
récursif
la source
la source
"asdf".match(/.*/g)
return ["asdf", ""]"aa".replace(/b*/, "b")
aboutirbabab
. Et à un moment donné, nous avons normalisé tous les détails de mise en œuvre des navigateurs Web.Réponses:
Conformément à la norme ECMA-262 , String.prototype.replace appelle RegExp.prototype [@@ replace] , qui dit:
où
rx
est/.*/g
etS
est'asdf'
.Voir 11.c.iii.2.b:
Par conséquent,
'asdf'.replace(/.*/g, 'x')
c'est en fait:[]
, lastIndex =0
'asdf'
, results =[ 'asdf' ]
, lastIndex =4
''
, = résultats[ 'asdf', '' ]
, lastIndex =4
,AdvanceStringIndex
, mis à lastIndex5
null
, résultats =[ 'asdf', '' ]
, retourIl y a donc 2 correspondances.
la source
'asdf'
une chaîne vide''
.Ensemble, dans une discussion hors ligne avec yawkat , nous avons trouvé un moyen intuitif de voir pourquoi
"abcd".replace(/.*/g, "x")
produit exactement deux correspondances. Notez que nous n'avons pas vérifié si elle est complètement égale à la sémantique imposée par la norme ECMAScript, il suffit donc de la prendre comme règle générale.Règles de base
(matchStr, matchIndex)
dans un ordre chronologique qui indique quelles parties de chaîne et indices de la chaîne d'entrée ont déjà été consommés.matchIndex
écrasant la sous-chaînematchStr
à cette position. SimatchStr = ""
, alors le "remplacement" est effectivement l'insertion.Formellement, l'acte d'appariement et de remplacement est décrit comme une boucle comme on le voit dans l'autre réponse .
Exemples simples
"abcd".replace(/.*/g, "x")
sorties"xx"
:La liste des correspondances est
[("abcd", 0), ("", 4)]
Notamment, il n'inclut pas les correspondances suivantes auxquelles on aurait pu penser pour les raisons suivantes:
("a", 0)
,("ab", 0)
: le quantificateur*
est gourmand("b", 1)
,("bc", 1)
: en raison du match précédent("abcd", 0)
, les cordes"b"
et"bc"
sont déjà mangées("", 4), ("", 4)
(ie deux fois): la position d'index 4 est déjà dévorée par la première correspondance apparentePar conséquent, la chaîne de
"x"
remplacement remplace les chaînes de correspondance trouvées exactement à ces positions: à la position 0, elle remplace la chaîne"abcd"
et à la position 4, elle remplace""
.Ici, vous pouvez voir que le remplacement peut agir comme un véritable remplacement d'une chaîne précédente ou tout simplement comme l'insertion d'une nouvelle chaîne.
"abcd".replace(/.*?/g, "x")
avec un quantificateur paresseux*?
sorties"xaxbxcxdx"
La liste des correspondances est
[("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]
Contrairement à l'exemple précédent, ici
("a", 0)
,("ab", 0)
,("abc", 0)
ou même("abcd", 0)
ne sont pas inclus en raison de la paresse qui limite strictement à trouver le plus possible de la correspondance quantificateurs.Étant donné que toutes les chaînes de correspondance sont vides, aucun remplacement réel ne se produit, mais des insertions de
x
aux positions 0, 1, 2, 3 et 4."abcd".replace(/.+?/g, "x")
avec un quantificateur paresseux+?
sorties"xxxx"
[("a", 0), ("b", 1), ("c", 2), ("d", 3)]
"abcd".replace(/.{2,}?/g, "x")
avec un quantificateur paresseux[2,}?
sorties"xx"
[("ab", 0), ("cd", 2)]
"abcd".replace(/.{0}/g, "x")
sorties"xaxbxcxdx"
par la même logique que dans l'exemple 2.Exemples plus difficiles
Nous pouvons toujours exploiter l'idée d' insertion au lieu de remplacement si nous faisons toujours correspondre une chaîne vide et contrôlons la position où de telles correspondances se produisent à notre avantage. Par exemple, nous pouvons créer des expressions régulières correspondant à la chaîne vide à chaque position paire pour y insérer un caractère:
"abcdefgh".replace(/(?<=^(..)*)/g, "_"))
avec un résultat positif derrière les(?<=...)
sorties"_ab_cd_ef_gh_"
(uniquement pris en charge dans Chrome jusqu'à présent)[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
"abcdefgh".replace(/(?=(..)*$)/g, "_"))
avec une sortie d' anticipation positive(?=...)
"_ab_cd_ef_gh_"
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
la source
while (!input not eaten up) { matchAndEat(); }
. De plus, les commentaires ci - dessus indiquent que le comportement est né bien avant l'existence de JavaScript.("abcd", 0)
ne mange pas la position 4 où irait le caractère suivant, mais la correspondance à zéro caractère le("", 4)
fait manger la position 4 où irait le personnage suivant. Si je concevais cela à partir de zéro, je pense que la règle que j'utiliserais est celle qui(str2, ix2)
peut suivre(str1, ix1)
ssiix2 >= ix1 + str1.length() && ix2 + str2.length() > ix1 + str1.length()
, ce qui ne provoque pas cette erreur.("abcd", 0)
ne mange pas la position 4 car il"abcd"
ne fait que 4 caractères et donc mange juste les indices 0, 1, 2, 3. Je peux voir d'où votre raisonnement pourrait provenir: pourquoi ne pouvons-nous pas avoir("abcd" ⋅ ε, 0)
une correspondance de 5 caractères où ⋅ est la concaténation etε
la correspondance de largeur nulle? Officiellement parce que"abcd" ⋅ ε = "abcd"
. J'ai pensé à une raison intuitive pour les dernières minutes, mais je n'ai pas réussi à en trouver une. Je suppose que l'on doit toujours traiterε
comme se produisant seul""
. J'adorerais jouer avec une implémentation alternative sans ce bug ou cet exploit. N'hésitez pas à partager!"" ⋅ ε = ""
, bien que je ne sois pas sûr de la distinction que vous avez l'intention de faire entre""
etε
, ce qui signifie la même chose). La différence ne peut donc pas être expliquée comme intuitive - elle l'est tout simplement.Le premier match est évidemment
"asdf"
(Position [0,4]). Parce que le drapeau mondial (g
) est défini, il continue la recherche. À ce stade (position 4), il trouve une deuxième correspondance, une chaîne vide (position [4,4]).N'oubliez pas que cela
*
correspond à zéro ou plusieurs éléments.la source
simplement, le premier
x
est pour le remplacement de l'appariementasdf
.deuxième
x
pour la chaîne vide aprèsasdf
. La recherche se termine lorsqu'elle est vide.la source