Natknąłem się na zaskakujący (dla mnie) fakt.
console.log("asdf".replace(/.*/g, "x"));
Dlaczego dwie zastępstwa? Wygląda na to, że jakikolwiek niepusty ciąg bez znaków nowej linii da dokładnie dwa zamienniki dla tego wzorca. Za pomocą funkcji zamiany widzę, że pierwsza zamiana dotyczy całego łańcucha, a druga pustego łańcucha.
javascript
regex
rekurencyjny
źródło
źródło
"asdf".match(/.*/g)
return [„asdf”, „”]"aa".replace(/b*/, "b")
skutkiem byłobabab
. W pewnym momencie ustandaryzowaliśmy wszystkie szczegóły implementacji przeglądarek internetowych.Odpowiedzi:
Zgodnie ze standardem ECMA-262 String.prototype.replace wywołuje RegExp.prototype [@@ replace] , który mówi:
gdzie
rx
jest/.*/g
iS
jest'asdf'
.Patrz 11.c.iii.2.b:
Dlatego w
'asdf'.replace(/.*/g, 'x')
rzeczywistości jest to:[]
, lastIndex =0
'asdf'
, wyniki =[ 'asdf' ]
, lastIndex =4
''
wyniki =[ 'asdf', '' ]
, = lastIndex4
,AdvanceStringIndex
ustaw lastIndex do5
null
, wyniki =[ 'asdf', '' ]
, powrótDlatego są 2 mecze.
źródło
'asdf'
i pusty ciąg''
.Wspólnie na czacie offline z yawkat znaleźliśmy intuicyjny sposób sprawdzenia, dlaczego
"abcd".replace(/.*/g, "x")
dokładnie produkuje dwa dopasowania. Zauważ, że nie sprawdziliśmy, czy całkowicie równa się semantyce narzuconej przez standard ECMAScript, dlatego po prostu weź to za ogólną zasadę.Reguły kciuka
(matchStr, matchIndex)
w kolejności chronologicznej, które wskazują, które części łańcucha i wskaźniki łańcucha wejściowego zostały już zjedzone.matchIndex
zastąpienie podłańcuchamatchStr
w tej pozycji. JeślimatchStr = ""
, to „zamiana” polega na wstawieniu.Formalnie czynność dopasowywania i zastępowania jest opisana jako pętla, jak widać w drugiej odpowiedzi .
Proste przykłady
"abcd".replace(/.*/g, "x")
wyjścia"xx"
:Lista meczów to
[("abcd", 0), ("", 4)]
W szczególności nie obejmuje następujących dopasowań, o których można pomyśleć z następujących powodów:
("a", 0)
,("ab", 0)
: kwantyfikator*
jest zachłanny("b", 1)
,("bc", 1)
: ze względu na poprzedni mecz("abcd", 0)
struny"b"
i"bc"
są już zjedzone("", 4), ("", 4)
(tj. dwa razy): pozycja indeksu 4 jest już pochłonięta przez pierwsze pozorne dopasowanieDlatego ciąg
"x"
zastępujący zastępuje znalezione pasujące ciągi dokładnie w tych pozycjach: w pozycji 0 zastępuje ciąg,"abcd"
a w pozycji 4 zastępuje""
.Tutaj widać, że zamiana może działać jak prawdziwe zastąpienie poprzedniego ciągu lub po prostu wstawienie nowego ciągu.
"abcd".replace(/.*?/g, "x")
z leniwymi*?
wyjściami kwantyfikatora"xaxbxcxdx"
Lista meczów to
[("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]
W przeciwieństwie do poprzedniego przykładu, tutaj
("a", 0)
,("ab", 0)
,("abc", 0)
, lub nawet("abcd", 0)
nie są wliczone powodu lenistwa kwantyfikatora, że ściśle ogranicza to, aby znaleźć możliwie najkrótszy mecz.Ponieważ wszystkie pasujące ciągi znaków są puste, nie występuje faktyczna zamiana, ale zamiast tego wstawienia
x
pozycji 0, 1, 2, 3 i 4."abcd".replace(/.+?/g, "x")
z leniwymi+?
wyjściami kwantyfikatora"xxxx"
[("a", 0), ("b", 1), ("c", 2), ("d", 3)]
"abcd".replace(/.{2,}?/g, "x")
z leniwymi[2,}?
wyjściami kwantyfikatora"xx"
[("ab", 0), ("cd", 2)]
"abcd".replace(/.{0}/g, "x")
wyprowadza"xaxbxcxdx"
tą samą logiką jak w przykładzie 2.Trudniejsze przykłady
Możemy konsekwentnie wykorzystywać ideę wstawiania zamiast zamiany, jeśli zawsze dopasowujemy pusty ciąg i kontrolujemy pozycję, w której takie dopasowania przynoszą nam korzyść. Na przykład możemy utworzyć wyrażenia regularne pasujące do pustego łańcucha w każdej parzystej pozycji, aby wstawić tam znak:
"abcdefgh".replace(/(?<=^(..)*)/g, "_"))
z pozytywnym wyglądem za(?<=...)
wyjściem"_ab_cd_ef_gh_"
(do tej pory obsługiwane tylko w Chrome)[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
"abcdefgh".replace(/(?=(..)*$)/g, "_"))
z A pozytywne lookAhead(?=...)
wyjść"_ab_cd_ef_gh_"
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
źródło
while (!input not eaten up) { matchAndEat(); }
. Ponadto powyższe komentarze wskazują, że zachowanie powstało dawno temu przed istnieniem JavaScript.("abcd", 0)
nie jeść pozycji 4, gdzie następujących znaków pójdzie, jeszcze mecz zero postać("", 4)
robi zjedz pozycję 4, do której poszedłaby następująca postać. Gdybym projektował to od zera, myślę, że zastosowałbym zasadę, która(str2, ix2)
może być zgodna z(str1, ix1)
iffix2 >= ix1 + str1.length() && ix2 + str2.length() > ix1 + str1.length()
, co nie powoduje tej mylności.("abcd", 0)
nie je pozycji 4, ponieważ"abcd"
ma tylko 4 znaki długości, a więc po prostu je indeksy 0, 1, 2, 3. Widzę, skąd pochodzi twoje rozumowanie: dlaczego nie możemy mieć("abcd" ⋅ ε, 0)
5-znakowego dopasowania, gdzie ⋅ jest konkatenacja iε
dopasowanie zerowej szerokości? Formalnie ponieważ"abcd" ⋅ ε = "abcd"
. Myślałem o intuicyjnym celu ostatnich minut, ale nie udało mi się go znaleźć. Wydaje mi się, że zawsze należy traktować toε
tak, jakby występowało samo z siebie""
. Chciałbym zagrać z alternatywną implementacją bez tego błędu lub wyczynu. Podziel się!"" ⋅ ε = ""
Chociaż nie jestem pewien, jakie rozróżnienie zamierzasz rozróżnić między""
iε
, co oznacza to samo). Tak więc różnicy nie można wyjaśnić jako intuicyjnej - po prostu jest.Pierwszy mecz to oczywiście
"asdf"
(pozycja [0,4]). Ponieważ ustawiono flagę globalną (g
), kontynuuje wyszukiwanie. W tym momencie (pozycja 4) znajduje drugie dopasowanie, pusty ciąg znaków (pozycja [4,4]).Pamiętaj, że
*
pasuje do zera lub więcej elementów.źródło