Czy istnieje wersja String.indexOf () JavaScript, która pozwala na wyrażenia regularne?

214

Czy w javascript istnieje odpowiednik String.indexOf (), który przyjmuje wyrażenie regularne zamiast ciągu dla pierwszego pierwszego parametru, jednocześnie dopuszczając drugi parametr?

Muszę zrobić coś takiego

str.indexOf(/[abc]/ , i);

i

str.lastIndexOf(/[abc]/ , i);

Podczas gdy String.search () przyjmuje wyrażenie regularne jako parametr, nie pozwala mi podać drugiego argumentu!

Edycja:
Okazało się to trudniejsze niż początkowo myślałem, dlatego napisałem małą funkcję testową do przetestowania wszystkich dostarczonych rozwiązań ... zakłada, że ​​do obiektu String zostały dodane regexIndexOf i regexLastIndexOf.

function test (str) {
    var i = str.length +2;
    while (i--) {
        if (str.indexOf('a',i) != str.regexIndexOf(/a/,i)) 
            alert (['failed regexIndexOf ' , str,i , str.indexOf('a',i) , str.regexIndexOf(/a/,i)]) ;
        if (str.lastIndexOf('a',i) != str.regexLastIndexOf(/a/,i) ) 
            alert (['failed regexLastIndexOf ' , str,i,str.lastIndexOf('a',i) , str.regexLastIndexOf(/a/,i)]) ;
    }
}

i testuję w następujący sposób, aby upewnić się, że przynajmniej dla jednego wyrażenia regularnego wynik jest taki sam, jak gdybyśmy użyli indexOf

// Poszukaj a wśród
testu xes („xxx”);
test („axx”);
test („xax”);
test („xxa”);
test („axa”);
test („xaa”);
test („aax”);
test („aaa”);

Poklepać
źródło
|wewnątrz [ ]pasuje do dosłownego charakteru |. Prawdopodobnie miałeś na myśli [abc].
Markus Jarderot,
tak, dziękuję, masz rację, naprawię to, ale sama regexp nie ma znaczenia ...
Pat
Zaktualizowałem moją odpowiedź Pat, dziękuję za wszelkie opinie.
Jason Bunting
Odkryłem, że prostszym i skuteczniejszym podejściem jest po prostu użycie string.match (/ [AZ] /). Jeśli nie ma wiele, metoda zwraca null, w przeciwnym razie otrzymasz obiekt, możesz dopasować (/ [AZ] /). Index, aby uzyskać indeks pierwszej
dużej

Odpowiedzi:

129

Łącząc kilka wspomnianych już podejść (indexOf jest oczywiście dość prosty), myślę, że są to funkcje, które wykonają tę sztuczkę:

String.prototype.regexIndexOf = function(regex, startpos) {
    var indexOf = this.substring(startpos || 0).search(regex);
    return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
    regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : ""));
    if(typeof (startpos) == "undefined") {
        startpos = this.length;
    } else if(startpos < 0) {
        startpos = 0;
    }
    var stringToWorkWith = this.substring(0, startpos + 1);
    var lastIndexOf = -1;
    var nextStop = 0;
    while((result = regex.exec(stringToWorkWith)) != null) {
        lastIndexOf = result.index;
        regex.lastIndex = ++nextStop;
    }
    return lastIndexOf;
}

Oczywiście modyfikacja wbudowanego obiektu String spowodowałaby wysłanie czerwonych flag dla większości ludzi, ale może to być jeden raz, gdy nie jest to aż tak wielka sprawa; po prostu bądź tego świadomy.


AKTUALIZACJA: Edytowana regexLastIndexOf()tak, aby wyglądała lastIndexOf()teraz naśladować . Daj mi znać, jeśli nadal nie działa i pod jakimi warunkami.


AKTUALIZACJA: Przechodzi wszystkie testy znalezione w komentarzach na tej stronie i moje. Oczywiście nie oznacza to, że jest kuloodporny. Wszelkie opinie są mile widziane.

Jason Bunting
źródło
Zwróci regexLastIndexOftylko indeks ostatniego nie pokrywającego się dopasowania.
Markus Jarderot
Przepraszam, ale nie OGROMNY facet od wyrażeń regularnych - czy możesz podać mi przykład, który sprawiłby, że mój upadłby? Doceniam to, że mogę dowiedzieć się więcej, ale twoja odpowiedź nie pomaga komuś tak nieświadomemu jak ja. :)
Jason Bunting
Jason Właśnie dodałem jakąś funkcję do przetestowania w pytaniu. to kończy się niepowodzeniem (między innymi testami) następującego „axx” .lastIndexOf („a”, 2)! = „axx” .regexLastIndexOf (/ a /, 2)
Pat
2
Myślę, że jest bardziej wydajny w użyciu regex.lastIndex = result.index + 1;zamiast regex.lastIndex = ++nextStop;. Mam nadzieję, że przejdzie do następnego meczu znacznie szybciej, nie tracąc żadnego wyniku.
Gedrox
1
Jeśli wolisz wyciągnąć go z npm, te dwie funkcje util są teraz w NPM jako: npmjs.com/package/index-of-regex
Capaj,
185

Instancje Stringkonstruktora mają .search()metodę, która akceptuje RegExp i zwraca indeks pierwszego dopasowania.

Aby rozpocząć wyszukiwanie od określonej pozycji (fałszowanie drugiego parametru .indexOf()), możesz slicewyłączyć pierwsze iznaki:

str.slice(i).search(/re/)

Ale to otrzyma indeks w krótszym ciągu (po odcięciu pierwszej części), więc będziesz chciał dodać długość odciętej części ( i) do zwróconego indeksu, jeśli tak nie było -1. To da ci indeks w oryginalnym ciągu:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}
Glenn
źródło
1
z pytania: Podczas gdy String.search () przyjmuje wyrażenie regularne jako parametr, nie pozwala mi podać drugiego argumentu!
Pat
14
str.substr (i) .search (/ re /)
Glenn
6
Świetne rozwiązanie, jednak wydajność jest nieco inna. indexOf zwróci liczbę od początku (niezależnie od przesunięcia), a to zwróci pozycję z przesunięcia. Tak więc, dla parzystości, będziesz chciał czegoś więcej takiego:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger
39

Mam dla ciebie krótką wersję. To działa dobrze dla mnie!

var match      = str.match(/[abc]/gi);
var firstIndex = str.indexOf(match[0]);
var lastIndex  = str.lastIndexOf(match[match.length-1]);

A jeśli chcesz prototypową wersję:

String.prototype.indexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.indexOf(match[0]) : -1;
}

String.prototype.lastIndexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.lastIndexOf(match[match.length-1]) : -1;
}

EDYCJA : jeśli chcesz dodać obsługę fromIndex

String.prototype.indexOfRegex = function(regex, fromIndex){
  var str = fromIndex ? this.substring(fromIndex) : this;
  var match = str.match(regex);
  return match ? str.indexOf(match[0]) + fromIndex : -1;
}

String.prototype.lastIndexOfRegex = function(regex, fromIndex){
  var str = fromIndex ? this.substring(0, fromIndex) : this;
  var match = str.match(regex);
  return match ? str.lastIndexOf(match[match.length-1]) : -1;
}

Aby z niego skorzystać, tak proste:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);
pmrotule
źródło
To naprawdę fajna sztuczka. Cóż, byłoby wspaniale, jeśli rozszerzysz go, aby wziąć startIndexparametr jak zwykle indeoxOfi lastIndexOfzrobić.
Robert Koritnik
@RobertKoritnik - zredagowałem swoją odpowiedź na wsparcie startIndex(lub fromIndex). Mam nadzieję, że to pomoże!
pmrotule
lastIndexOfRegexpowinien również dodać wartość fromIndexdo wyniku.
Peter
Twój algorytm rozpadł się w następującym scenariuszu: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Wynik wyniesie 1, gdy powinien wynosić 7, ponieważ indexOf po raz pierwszy wyszuka „romeo”, bez względu na to, czy jest na początku słowa, czy nie.
KorelK
13

Posługiwać się:

str.search(regex)

Zobacz dokumentację tutaj.

rmg.n3t
źródło
11
@OZZIE: Nie, nie bardzo. Jest to w zasadzie odpowiedź Glenna (z ~ 150 głosami pozytywnymi), tyle że nie ma żadnego wyjaśnienia , nie obsługuje pozycji wyjściowej innej niż 0i została opublikowana ... siedem lat później.
ccjmne
7

Na podstawie odpowiedzi BaileyP. Główną różnicą jest to, że metody te zwracają się, -1jeśli wzorca nie można dopasować.

Edycja: Dzięki odpowiedzi Jasona Buntinga wpadłem na pomysł. Dlaczego nie zmodyfikować .lastIndexwłaściwości wyrażenia regularnego? Chociaż będzie to działać tylko w przypadku wzorców z flagą globalną ( /g).

Edycja: Zaktualizowano, aby przekazać przypadki testowe.

String.prototype.regexIndexOf = function(re, startPos) {
    startPos = startPos || 0;

    if (!re.global) {
        var flags = "g" + (re.multiline?"m":"") + (re.ignoreCase?"i":"");
        re = new RegExp(re.source, flags);
    }

    re.lastIndex = startPos;
    var match = re.exec(this);

    if (match) return match.index;
    else return -1;
}

String.prototype.regexLastIndexOf = function(re, startPos) {
    startPos = startPos === undefined ? this.length : startPos;

    if (!re.global) {
        var flags = "g" + (re.multiline?"m":"") + (re.ignoreCase?"i":"");
        re = new RegExp(re.source, flags);
    }

    var lastSuccess = -1;
    for (var pos = 0; pos <= startPos; pos++) {
        re.lastIndex = pos;

        var match = re.exec(this);
        if (!match) break;

        pos = match.index;
        if (pos <= startPos) lastSuccess = pos;
    }

    return lastSuccess;
}
Markus Jarderot
źródło
Jak dotąd wydaje się to najbardziej obiecujące (po kilku poprawkach sytax) :-) Tylko nieudane kilka testów warunków brzegowych. Rzeczy takie jak 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Rozglądam się, aby sprawdzić, czy mogę naprawić te przypadki
Pat
6

Możesz użyć substr.

str.substr(i).match(/[abc]/);
Andru Luvisi
źródło
Ze znanej książki JavaScript opublikowanej przez O'Reilly: „substr nie został ustandaryzowany przez ECMAScript i dlatego jest przestarzały”. Ale podoba mi się podstawowa idea tego, do czego zmierzasz.
Jason Bunting
1
To nie jest problem. Jeśli NAPRAWDĘ martwisz się tym, użyj zamiast tego String.substring () - po prostu musisz wykonać matematykę nieco inaczej. Poza tym JavaScript nie powinien być w 100% oparty na języku nadrzędnym.
Peter Bailey,
To nie jest żaden problem - jeśli twój kod działa z implementacją, która nie implementuje substr, ponieważ chcą przestrzegać standardów ECMAScript, będziesz mieć problemy. To prawda, że ​​zastąpienie go podciągiem nie jest tak trudne, ale dobrze jest o tym wiedzieć.
Jason Bunting
1
W momencie wystąpienia problemów masz bardzo proste rozwiązania. Myślę, że komentarze są rozsądne, ale głosowanie w dół było pedantyczne.
VoronoiPotato
Czy możesz edytować swoją odpowiedź, aby zapewnić działający kod demonstracyjny?
vsync
5

RexExpinstancje mają już właściwość lastIndex (jeśli są globalne), więc to, co robię, to kopiowanie wyrażenia regularnego, modyfikowanie go nieznacznie, aby pasowało do naszych celów, - umieszczanie execgo na łańcuchu znaków i przeglądanie lastIndex. To nieuchronnie będzie szybsze niż zapętlanie łańcucha. (Masz wystarczająco dużo przykładów, jak umieścić to w prototypie łańcucha, prawda?)

function reIndexOf(reIn, str, startIndex) {
    var re = new RegExp(reIn.source, 'g' + (reIn.ignoreCase ? 'i' : '') + (reIn.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

function reLastIndexOf(reIn, str, startIndex) {
    var src = /\$$/.test(reIn.source) && !/\\\$$/.test(reIn.source) ? reIn.source : reIn.source + '(?![\\S\\s]*' + reIn.source + ')';
    var re = new RegExp(src, 'g' + (reIn.ignoreCase ? 'i' : '') + (reIn.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

reIndexOf(/[abc]/, "tommy can eat");  // Returns 6
reIndexOf(/[abc]/, "tommy can eat", 8);  // Returns 11
reLastIndexOf(/[abc]/, "tommy can eat"); // Returns 11

Możesz także prototypować funkcje na obiekcie RegExp:

RegExp.prototype.indexOf = function(str, startIndex) {
    var re = new RegExp(this.source, 'g' + (this.ignoreCase ? 'i' : '') + (this.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};

RegExp.prototype.lastIndexOf = function(str, startIndex) {
    var src = /\$$/.test(this.source) && !/\\\$$/.test(this.source) ? this.source : this.source + '(?![\\S\\s]*' + this.source + ')';
    var re = new RegExp(src, 'g' + (this.ignoreCase ? 'i' : '') + (this.multiLine ? 'm' : ''));
    re.lastIndex = startIndex || 0;
    var res = re.exec(str);
    if(!res) return -1;
    return re.lastIndex - res[0].length;
};


/[abc]/.indexOf("tommy can eat");  // Returns 6
/[abc]/.indexOf("tommy can eat", 8);  // Returns 11
/[abc]/.lastIndexOf("tommy can eat"); // Returns 11

Krótkie wyjaśnienie, w jaki sposób modyfikuję RegExp: Po indexOfprostu muszę się upewnić, że globalna flaga jest ustawiona. Dla lastIndexOfużywam negatywnego wybiegania w przyszłość, aby znaleźć ostatnie wystąpienie, chyba że RegExpbyło już zgodne na końcu ciągu.

Prestaul
źródło
4

Nie działa natywnie, ale z pewnością możesz dodać tę funkcjonalność

<script type="text/javascript">

String.prototype.regexIndexOf = function( pattern, startIndex )
{
    startIndex = startIndex || 0;
    var searchResult = this.substr( startIndex ).search( pattern );
    return ( -1 === searchResult ) ? -1 : searchResult + startIndex;
}

String.prototype.regexLastIndexOf = function( pattern, startIndex )
{
    startIndex = startIndex === undefined ? this.length : startIndex;
    var searchResult = this.substr( 0, startIndex ).reverse().regexIndexOf( pattern, 0 );
    return ( -1 === searchResult ) ? -1 : this.length - ++searchResult;
}

String.prototype.reverse = function()
{
    return this.split('').reverse().join('');
}

// Indexes 0123456789
var str = 'caabbccdda';

alert( [
        str.regexIndexOf( /[cd]/, 4 )
    ,   str.regexLastIndexOf( /[cd]/, 4 )
    ,   str.regexIndexOf( /[yz]/, 4 )
    ,   str.regexLastIndexOf( /[yz]/, 4 )
    ,   str.lastIndexOf( 'd', 4 )
    ,   str.regexLastIndexOf( /d/, 4 )
    ,   str.lastIndexOf( 'd' )
    ,   str.regexLastIndexOf( /d/ )
    ]
);

</script>

Nie w pełni przetestowałem te metody, ale wydaje się, że do tej pory działały.

Peter Bailey
źródło
Zaktualizowano w celu obsługi tych spraw
Peter Bailey,
za każdym razem, gdy mam zamiar zaakceptować tę odpowiedź, znajduję nowy przypadek! Dają różne wyniki! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat
no cóż, oczywiście, że są - str.lastIndexOf zrobi przymus typu na wzorcu - przekształcając go w ciąg. Ciąg „/ [d] /” z pewnością nie został znaleziony na wejściu, więc zwrócone -1 jest w rzeczywistości dokładne.
Peter Bailey,
Rozumiem. Po przeczytaniu specyfikacji String.lastIndexOf () - po prostu źle zrozumiałem, jak działa ten argument. Ta nowa wersja powinna sobie z tym poradzić.
Peter Bailey,
Coś jest nadal nie tak, ale robi się już późno ... Spróbuję dostać testowy przypadek i może go naprawić rano. Przepraszamy za dotychczasowe kłopoty.
Pat
2

Po tym, jak wszystkie proponowane rozwiązania zawiodły w ten czy inny sposób (edytuj: niektóre zostały zaktualizowane, aby przejść testy po tym, jak to napisałem), znalazłem implementację Mozilli dla Array.indexOf i Array.lastIndexOf

Użyłem ich do wdrożenia mojej wersji String.prototype.regexIndexOf i String.prototype.regexLastIndexOf w następujący sposób:

String.prototype.regexIndexOf = function(elt /*, from*/)
  {
    var arr = this.split('');
    var len = arr.length;

    var from = Number(arguments[1]) || 0;
    from = (from < 0) ? Math.ceil(from) : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++) {
      if (from in arr && elt.exec(arr[from]) ) 
        return from;
    }
    return -1;
};

String.prototype.regexLastIndexOf = function(elt /*, from*/)
  {
    var arr = this.split('');
    var len = arr.length;

    var from = Number(arguments[1]);
    if (isNaN(from)) {
      from = len - 1;
    } else {
      from = (from < 0) ? Math.ceil(from) : Math.floor(from);
      if (from < 0)
        from += len;
      else if (from >= len)
        from = len - 1;
    }

    for (; from > -1; from--) {
      if (from in arr && elt.exec(arr[from]) )
        return from;
    }
    return -1;
  };

Wydają się zdawać funkcje testowe, które podałem w pytaniu.

Oczywiście działają one tylko wtedy, gdy wyrażenie regularne pasuje do jednego znaku, ale to jest wystarczające dla mojego celu, ponieważ będę go używać do rzeczy takich jak ([abc], \ s, \ W, \ D)

Będę nadal monitorować to pytanie, na wypadek gdyby ktoś zapewnił lepszą / szybszą / czystszą / bardziej ogólną implementację, która działa na dowolnym wyrażeniu regularnym.

Poklepać
źródło
Wow, to długi kawałek kodu. Sprawdź moją zaktualizowaną odpowiedź i prześlij opinię. Dzięki.
Jason Bunting,
Ta implementacja ma na celu absolutną kompatybilność z lastIndexOf w Firefoksie i silnikiem JavaScript SpiderMonkey, w tym w kilku przypadkach, które prawdopodobnie są przypadkami skrajnymi. [...] w rzeczywistych aplikacjach możesz obliczyć na podstawie mniej skomplikowanego kodu, jeśli zignorujesz te przypadki.
Pat
Utwórz stronę mozilli :-) Właśnie wziąłem kod reklamy i zmieniłem dwa wiersze, pozostawiając wszystkie przypadki na krawędzi. Ponieważ kilka innych odpowiedzi zostało zaktualizowanych, aby przejść testy, spróbuję je porównać i zaakceptować najbardziej efektywne. Kiedy mam czas na ponowne rozpatrzenie problemu.
Pat
Zaktualizowałem swoje rozwiązanie i doceniam wszelkie opinie lub rzeczy, które powodują jego awarię. Dokonałem zmiany, aby naprawić nakładający się problem wskazany przez MizardX (mam nadzieję!)
Jason Bunting
2

Potrzebowałem regexIndexOffunkcji również dla tablicy, więc sam ją zaprogramowałem. Wątpię jednak, czy jest zoptymalizowany, ale myślę, że powinien działać poprawnie.

Array.prototype.regexIndexOf = function (regex, startpos = 0) {
    len = this.length;
    for(x = startpos; x < len; x++){
        if(typeof this[x] != 'undefined' && (''+this[x]).match(regex)){
            return x;
        }
    }
    return -1;
}

arr = [];
arr.push(null);
arr.push(NaN);
arr[3] = 7;
arr.push('asdf');
arr.push('qwer');
arr.push(9);
arr.push('...');
console.log(arr);
arr.regexIndexOf(/\d/, 4);
jakov
źródło
1

W niektórych prostych przypadkach możesz uprościć wyszukiwanie do tyłu za pomocą podziału.

function regexlast(string,re){
  var tokens=string.split(re);
  return (tokens.length>1)?(string.length-tokens[tokens.length-1].length):null;
}

Ma to kilka poważnych problemów:

  1. nakładające się mecze nie pojawią się
  2. zwrócony indeks dotyczy końca dopasowania, a nie początku (w porządku, jeśli wyrażenie regularne jest stałą)

Ale z drugiej strony jest to o wiele mniej kodu. W przypadku wyrażenia regularnego o stałej długości, które nie mogą się pokrywać (np. W /\s\w/celu znalezienia granic słów), jest to wystarczające.

amwinter
źródło
0

W przypadku danych z rzadkimi dopasowaniami użycie ciągu string.search jest najszybsze w przeglądarkach. Ponownie kroi ciąg po każdej iteracji, aby:

function lastIndexOfSearch(string, regex, index) {
  if(index === 0 || index)
     string = string.slice(0, Math.max(0,index));
  var idx;
  var offset = -1;
  while ((idx = string.search(regex)) !== -1) {
    offset += idx + 1;
    string = string.slice(idx + 1);
  }
  return offset;
}

Zrobiłem to dla gęstych danych. Jest skomplikowany w porównaniu do metody wykonywania, ale w przypadku gęstych danych jest 2-10 razy szybszy niż każda inna metoda, którą wypróbowałem, i około 100 razy szybciej niż przyjęte rozwiązanie. Główne punkty to:

  1. Wywołuje exec w wyrażeniu regularnym przekazanym raz, aby sprawdzić, czy istnieje dopasowanie lub wyjść wcześniej. Robię to za pomocą (? = W podobnej metodzie, ale w IE sprawdzanie za pomocą exec jest znacznie szybsze.
  2. Konstruuje i buforuje zmodyfikowane wyrażenie regularne w formacie „(r). (?!. ? r) ”
  3. Nowe wyrażenie regularne jest wykonywane i zwracane są wyniki zarówno tego, jak i pierwszego exec;

    function lastIndexOfGroupSimple(string, regex, index) {
        if (index === 0 || index) string = string.slice(0, Math.max(0, index + 1));
        regex.lastIndex = 0;
        var lastRegex, index
        flags = 'g' + (regex.multiline ? 'm' : '') + (regex.ignoreCase ? 'i' : ''),
        key = regex.source + '$' + flags,
        match = regex.exec(string);
        if (!match) return -1;
        if (lastIndexOfGroupSimple.cache === undefined) lastIndexOfGroupSimple.cache = {};
        lastRegex = lastIndexOfGroupSimple.cache[key];
        if (!lastRegex)
            lastIndexOfGroupSimple.cache[key] = lastRegex = new RegExp('.*(' + regex.source + ')(?!.*?' + regex.source + ')', flags);
        index = match.index;
        lastRegex.lastIndex = match.index;
        return (match = lastRegex.exec(string)) ? lastRegex.lastIndex - match[1].length : index;
    };

jsPerf metod

Nie rozumiem celu testów do góry. Sytuacje wymagające wyrażenia regularnego są niemożliwe do porównania z wywołaniem indexOf, które moim zdaniem jest celem stworzenia metody w pierwszej kolejności. Aby pomyślnie przejść test, bardziej sensowne jest użycie „xxx + (?! x)” niż dostosowanie iteracji wyrażenia regularnego.

npjohns
źródło
0

Ostatni indeks Jasona Buntinga nie działa. Mój nie jest optymalny, ale działa.

//Jason Bunting's
String.prototype.regexIndexOf = function(regex, startpos) {
var indexOf = this.substring(startpos || 0).search(regex);
return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
var lastIndex = -1;
var index = this.regexIndexOf( regex );
startpos = startpos === undefined ? this.length : startpos;

while ( index >= 0 && index < startpos )
{
    lastIndex = index;
    index = this.regexIndexOf( regex, index + 1 );
}
return lastIndex;
}
Eli
źródło
Czy możesz podać test, który powoduje, że mój nie działa? Jeśli stwierdzisz, że to nie działa, podaj przypadek testowy, dlaczego po prostu powiedz „to nie działa” i zapewnij nieoptymalne rozwiązanie?
Jason Bunting,
Hoo boy. Masz całkowitą rację. Powinienem był podać przykład. Niestety przeszedłem z tego kodu kilka miesięcy temu i nie mam pojęcia, co było przyczyną niepowodzenia. : - /
Eli
takie jest życie. :)
Jason Bunting
0

Nadal nie ma natywnych metod wykonujących żądane zadanie.

Oto kod, którego używam. Naśladuje on zachowanie metod String.prototype.indexOf i String.prototype.lastIndexOf , ale akceptuje także RegExp jako argument wyszukiwania oprócz łańcucha reprezentującego wartość do wyszukania.

Tak, odpowiedź jest dość długa, ponieważ stara się jak najlepiej przestrzegać aktualnych standardów i oczywiście zawiera rozsądną liczbę komentarzy JSDOC . Jednak po zminimalizowaniu kod ma tylko 2,27 KB, a po skompresowaniu do gzipa ma tylko 1023 bajtów.

Dwie metody, które to dodaje String.prototype(używając Object.defineProperty, jeśli są dostępne) to:

  1. searchOf
  2. searchLastOf

Przeszedł wszystkie testy opublikowane przez PO, a dodatkowo dość dokładnie przetestowałem procedury w moim codziennym użyciu i starałem się mieć pewność, że działają one w wielu środowiskach, ale opinie i problemy są zawsze mile widziane.

/*jslint maxlen:80, browser:true */

/*
 * Properties used by searchOf and searchLastOf implementation.
 */

/*property
    MAX_SAFE_INTEGER, abs, add, apply, call, configurable, defineProperty,
    enumerable, exec, floor, global, hasOwnProperty, ignoreCase, index,
    lastIndex, lastIndexOf, length, max, min, multiline, pow, prototype,
    remove, replace, searchLastOf, searchOf, source, toString, value, writable
*/

/*
 * Properties used in the testing of searchOf and searchLastOf implimentation.
 */

/*property
    appendChild, createTextNode, getElementById, indexOf, lastIndexOf, length,
    searchLastOf, searchOf, unshift
*/

(function () {
    'use strict';

    var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1,
        getNativeFlags = new RegExp('\\/([a-z]*)$', 'i'),
        clipDups = new RegExp('([\\s\\S])(?=[\\s\\S]*\\1)', 'g'),
        pToString = Object.prototype.toString,
        pHasOwn = Object.prototype.hasOwnProperty,
        stringTagRegExp;

    /**
     * Defines a new property directly on an object, or modifies an existing
     * property on an object, and returns the object.
     *
     * @private
     * @function
     * @param {Object} object
     * @param {string} property
     * @param {Object} descriptor
     * @returns {Object}
     * @see https://goo.gl/CZnEqg
     */
    function $defineProperty(object, property, descriptor) {
        if (Object.defineProperty) {
            Object.defineProperty(object, property, descriptor);
        } else {
            object[property] = descriptor.value;
        }

        return object;
    }

    /**
     * Returns true if the operands are strictly equal with no type conversion.
     *
     * @private
     * @function
     * @param {*} a
     * @param {*} b
     * @returns {boolean}
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.4
     */
    function $strictEqual(a, b) {
        return a === b;
    }

    /**
     * Returns true if the operand inputArg is undefined.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {boolean}
     */
    function $isUndefined(inputArg) {
        return $strictEqual(typeof inputArg, 'undefined');
    }

    /**
     * Provides a string representation of the supplied object in the form
     * "[object type]", where type is the object type.
     *
     * @private
     * @function
     * @param {*} inputArg The object for which a class string represntation
     *                     is required.
     * @returns {string} A string value of the form "[object type]".
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2
     */
    function $toStringTag(inputArg) {
        var val;
        if (inputArg === null) {
            val = '[object Null]';
        } else if ($isUndefined(inputArg)) {
            val = '[object Undefined]';
        } else {
            val = pToString.call(inputArg);
        }

        return val;
    }

    /**
     * The string tag representation of a RegExp object.
     *
     * @private
     * @type {string}
     */
    stringTagRegExp = $toStringTag(getNativeFlags);

    /**
     * Returns true if the operand inputArg is a RegExp.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {boolean}
     */
    function $isRegExp(inputArg) {
        return $toStringTag(inputArg) === stringTagRegExp &&
                pHasOwn.call(inputArg, 'ignoreCase') &&
                typeof inputArg.ignoreCase === 'boolean' &&
                pHasOwn.call(inputArg, 'global') &&
                typeof inputArg.global === 'boolean' &&
                pHasOwn.call(inputArg, 'multiline') &&
                typeof inputArg.multiline === 'boolean' &&
                pHasOwn.call(inputArg, 'source') &&
                typeof inputArg.source === 'string';
    }

    /**
     * The abstract operation throws an error if its argument is a value that
     * cannot be converted to an Object, otherwise returns the argument.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be tested.
     * @throws {TypeError} If inputArg is null or undefined.
     * @returns {*} The inputArg if coercible.
     * @see https://goo.gl/5GcmVq
     */
    function $requireObjectCoercible(inputArg) {
        var errStr;

        if (inputArg === null || $isUndefined(inputArg)) {
            errStr = 'Cannot convert argument to object: ' + inputArg;
            throw new TypeError(errStr);
        }

        return inputArg;
    }

    /**
     * The abstract operation converts its argument to a value of type string
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {string}
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tostring
     */
    function $toString(inputArg) {
        var type,
            val;

        if (inputArg === null) {
            val = 'null';
        } else {
            type = typeof inputArg;
            if (type === 'string') {
                val = inputArg;
            } else if (type === 'undefined') {
                val = type;
            } else {
                if (type === 'symbol') {
                    throw new TypeError('Cannot convert symbol to string');
                }

                val = String(inputArg);
            }
        }

        return val;
    }

    /**
     * Returns a string only if the arguments is coercible otherwise throws an
     * error.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @throws {TypeError} If inputArg is null or undefined.
     * @returns {string}
     */
    function $onlyCoercibleToString(inputArg) {
        return $toString($requireObjectCoercible(inputArg));
    }

    /**
     * The function evaluates the passed value and converts it to an integer.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to an integer.
     * @returns {number} If the target value is NaN, null or undefined, 0 is
     *                   returned. If the target value is false, 0 is returned
     *                   and if true, 1 is returned.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
     */
    function $toInteger(inputArg) {
        var number = +inputArg,
            val = 0;

        if ($strictEqual(number, number)) {
            if (!number || number === Infinity || number === -Infinity) {
                val = number;
            } else {
                val = (number > 0 || -1) * Math.floor(Math.abs(number));
            }
        }

        return val;
    }

    /**
     * Copies a regex object. Allows adding and removing native flags while
     * copying the regex.
     *
     * @private
     * @function
     * @param {RegExp} regex Regex to copy.
     * @param {Object} [options] Allows specifying native flags to add or
     *                           remove while copying the regex.
     * @returns {RegExp} Copy of the provided regex, possibly with modified
     *                   flags.
     */
    function $copyRegExp(regex, options) {
        var flags,
            opts,
            rx;

        if (options !== null && typeof options === 'object') {
            opts = options;
        } else {
            opts = {};
        }

        // Get native flags in use
        flags = getNativeFlags.exec($toString(regex))[1];
        flags = $onlyCoercibleToString(flags);
        if (opts.add) {
            flags += opts.add;
            flags = flags.replace(clipDups, '');
        }

        if (opts.remove) {
            // Would need to escape `options.remove` if this was public
            rx = new RegExp('[' + opts.remove + ']+', 'g');
            flags = flags.replace(rx, '');
        }

        return new RegExp(regex.source, flags);
    }

    /**
     * The abstract operation ToLength converts its argument to an integer
     * suitable for use as the length of an array-like object.
     *
     * @private
     * @function
     * @param {*} inputArg The object to be converted to a length.
     * @returns {number} If len <= +0 then +0 else if len is +INFINITY then
     *                   2^53-1 else min(len, 2^53-1).
     * @see https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
     */
    function $toLength(inputArg) {
        return Math.min(Math.max($toInteger(inputArg), 0), MAX_SAFE_INTEGER);
    }

    /**
     * Copies a regex object so that it is suitable for use with searchOf and
     * searchLastOf methods.
     *
     * @private
     * @function
     * @param {RegExp} regex Regex to copy.
     * @returns {RegExp}
     */
    function $toSearchRegExp(regex) {
        return $copyRegExp(regex, {
            add: 'g',
            remove: 'y'
        });
    }

    /**
     * Returns true if the operand inputArg is a member of one of the types
     * Undefined, Null, Boolean, Number, Symbol, or String.
     *
     * @private
     * @function
     * @param {*} inputArg
     * @returns {boolean}
     * @see https://goo.gl/W68ywJ
     * @see https://goo.gl/ev7881
     */
    function $isPrimitive(inputArg) {
        var type = typeof inputArg;

        return type === 'undefined' ||
                inputArg === null ||
                type === 'boolean' ||
                type === 'string' ||
                type === 'number' ||
                type === 'symbol';
    }

    /**
     * The abstract operation converts its argument to a value of type Object
     * but fixes some environment bugs.
     *
     * @private
     * @function
     * @param {*} inputArg The argument to be converted to an object.
     * @throws {TypeError} If inputArg is not coercible to an object.
     * @returns {Object} Value of inputArg as type Object.
     * @see http://www.ecma-international.org/ecma-262/5.1/#sec-9.9
     */
    function $toObject(inputArg) {
        var object;

        if ($isPrimitive($requireObjectCoercible(inputArg))) {
            object = Object(inputArg);
        } else {
            object = inputArg;
        }

        return object;
    }

    /**
     * Converts a single argument that is an array-like object or list (eg.
     * arguments, NodeList, DOMTokenList (used by classList), NamedNodeMap
     * (used by attributes property)) into a new Array() and returns it.
     * This is a partial implementation of the ES6 Array.from
     *
     * @private
     * @function
     * @param {Object} arrayLike
     * @returns {Array}
     */
    function $toArray(arrayLike) {
        var object = $toObject(arrayLike),
            length = $toLength(object.length),
            array = [],
            index = 0;

        array.length = length;
        while (index < length) {
            array[index] = object[index];
            index += 1;
        }

        return array;
    }

    if (!String.prototype.searchOf) {
        /**
         * This method returns the index within the calling String object of
         * the first occurrence of the specified value, starting the search at
         * fromIndex. Returns -1 if the value is not found.
         *
         * @function
         * @this {string}
         * @param {RegExp|string} regex A regular expression object or a String.
         *                              Anything else is implicitly converted to
         *                              a String.
         * @param {Number} [fromIndex] The location within the calling string
         *                             to start the search from. It can be any
         *                             integer. The default value is 0. If
         *                             fromIndex < 0 the entire string is
         *                             searched (same as passing 0). If
         *                             fromIndex >= str.length, the method will
         *                             return -1 unless searchValue is an empty
         *                             string in which case str.length is
         *                             returned.
         * @returns {Number} If successful, returns the index of the first
         *                   match of the regular expression inside the
         *                   string. Otherwise, it returns -1.
         */
        $defineProperty(String.prototype, 'searchOf', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (regex) {
                var str = $onlyCoercibleToString(this),
                    args = $toArray(arguments),
                    result = -1,
                    fromIndex,
                    match,
                    rx;

                if (!$isRegExp(regex)) {
                    return String.prototype.indexOf.apply(str, args);
                }

                if ($toLength(args.length) > 1) {
                    fromIndex = +args[1];
                    if (fromIndex < 0) {
                        fromIndex = 0;
                    }
                } else {
                    fromIndex = 0;
                }

                if (fromIndex >= $toLength(str.length)) {
                    return result;
                }

                rx = $toSearchRegExp(regex);
                rx.lastIndex = fromIndex;
                match = rx.exec(str);
                if (match) {
                    result = +match.index;
                }

                return result;
            }
        });
    }

    if (!String.prototype.searchLastOf) {
        /**
         * This method returns the index within the calling String object of
         * the last occurrence of the specified value, or -1 if not found.
         * The calling string is searched backward, starting at fromIndex.
         *
         * @function
         * @this {string}
         * @param {RegExp|string} regex A regular expression object or a String.
         *                              Anything else is implicitly converted to
         *                              a String.
         * @param {Number} [fromIndex] Optional. The location within the
         *                             calling string to start the search at,
         *                             indexed from left to right. It can be
         *                             any integer. The default value is
         *                             str.length. If it is negative, it is
         *                             treated as 0. If fromIndex > str.length,
         *                             fromIndex is treated as str.length.
         * @returns {Number} If successful, returns the index of the first
         *                   match of the regular expression inside the
         *                   string. Otherwise, it returns -1.
         */
        $defineProperty(String.prototype, 'searchLastOf', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (regex) {
                var str = $onlyCoercibleToString(this),
                    args = $toArray(arguments),
                    result = -1,
                    fromIndex,
                    length,
                    match,
                    pos,
                    rx;

                if (!$isRegExp(regex)) {
                    return String.prototype.lastIndexOf.apply(str, args);
                }

                length = $toLength(str.length);
                if (!$strictEqual(args[1], args[1])) {
                    fromIndex = length;
                } else {
                    if ($toLength(args.length) > 1) {
                        fromIndex = $toInteger(args[1]);
                    } else {
                        fromIndex = length - 1;
                    }
                }

                if (fromIndex >= 0) {
                    fromIndex = Math.min(fromIndex, length - 1);
                } else {
                    fromIndex = length - Math.abs(fromIndex);
                }

                pos = 0;
                rx = $toSearchRegExp(regex);
                while (pos <= fromIndex) {
                    rx.lastIndex = pos;
                    match = rx.exec(str);
                    if (!match) {
                        break;
                    }

                    pos = +match.index;
                    if (pos <= fromIndex) {
                        result = pos;
                    }

                    pos += 1;
                }

                return result;
            }
        });
    }
}());

(function () {
    'use strict';

    /*
     * testing as follow to make sure that at least for one character regexp,
     * the result is the same as if we used indexOf
     */

    var pre = document.getElementById('out');

    function log(result) {
        pre.appendChild(document.createTextNode(result + '\n'));
    }

    function test(str) {
        var i = str.length + 2,
            r,
            a,
            b;

        while (i) {
            a = str.indexOf('a', i);
            b = str.searchOf(/a/, i);
            r = ['Failed', 'searchOf', str, i, a, b];
            if (a === b) {
                r[0] = 'Passed';
            }

            log(r);
            a = str.lastIndexOf('a', i);
            b = str.searchLastOf(/a/, i);
            r = ['Failed', 'searchLastOf', str, i, a, b];
            if (a === b) {
                r[0] = 'Passed';
            }

            log(r);
            i -= 1;
        }
    }

    /*
     * Look for the a among the xes
     */

    test('xxx');
    test('axx');
    test('xax');
    test('xxa');
    test('axa');
    test('xaa');
    test('aax');
    test('aaa');
}());
<pre id="out"></pre>

Xotic750
źródło
0

Jeśli szukasz bardzo prostego wyszukiwania lastIndex za pomocą RegExp i nie obchodzi Cię, czy naśladuje lastIndexOf do ostatniego szczegółu, może to przykuć twoją uwagę.

Po prostu odwracam ciąg i odejmuję indeks pierwszego wystąpienia od długości - 1. Zdarza się, że mój test jest pozytywny, ale myślę, że może wystąpić problem z wydajnością przy długich ciągach.

interface String {
  reverse(): string;
  lastIndex(regex: RegExp): number;
}

String.prototype.reverse = function(this: string) {
  return this.split("")
    .reverse()
    .join("");
};

String.prototype.lastIndex = function(this: string, regex: RegExp) {
  const exec = regex.exec(this.reverse());
  return exec === null ? -1 : this.length - 1 - exec.index;
};
Reijo
źródło
0

Użyłem, String.prototype.match(regex)który zwraca tablicę ciągów wszystkich znalezionych dopasowań podanych regexw ciągu (więcej informacji tutaj ):

function getLastIndex(text, regex, limit = text.length) {
  const matches = text.match(regex);

  // no matches found
  if (!matches) {
    return -1;
  }

  // matches found but first index greater than limit
  if (text.indexOf(matches[0] + matches[0].length) > limit) {
    return -1;
  }

  // reduce index until smaller than limit
  let i = matches.length - 1;
  let index = text.lastIndexOf(matches[i]);
  while (index > limit && i >= 0) {
    i--;
    index = text.lastIndexOf(matches[i]);
  }
  return index > limit ? -1 : index;
}

// expect -1 as first index === 14
console.log(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g, 10));

// expect 29
console.log(getLastIndex('First Sentence. Last Sentence. Unfinished', /\. /g));

wfreude
źródło
0
var mystring = "abc ab a";
var re  = new RegExp("ab"); // any regex here

if ( re.exec(mystring) != null ){ 
   alert("matches"); // true in this case
}

Użyj standardowych wyrażeń regularnych:

var re  = new RegExp("^ab");  // At front
var re  = new RegExp("ab$");  // At end
var re  = new RegExp("ab(c|d)");  // abc or abd
użytkownik984003
źródło
-2

Cóż, ponieważ po prostu chcesz dopasować pozycję postaci , regex jest prawdopodobnie przesadny.

Zakładam, że wszystko czego chcesz to zamiast „znajdź pierwszą z tych postaci”, po prostu znajdź pierwszą z tych postaci.

Jest to oczywiście prosta odpowiedź, ale robi to, co postawiono w pytaniu, aczkolwiek bez części wyrażenia regularnego (ponieważ nie wyjaśniłeś, dlaczego konkretnie miało to być wyrażenie regularne)

function mIndexOf( str , chars, offset )
{
   var first  = -1; 
   for( var i = 0; i < chars.length;  i++ )
   {
      var p = str.indexOf( chars[i] , offset ); 
      if( p < first || first === -1 )
      {
           first = p;
      }
   }
   return first; 
}
String.prototype.mIndexOf = function( chars, offset )
{
   return mIndexOf( this, chars, offset ); # I'm really averse to monkey patching.  
};
mIndexOf( "hello world", ['a','o','w'], 0 );
>> 4 
mIndexOf( "hello world", ['a'], 0 );
>> -1 
mIndexOf( "hello world", ['a','o','w'], 4 );
>> 4
mIndexOf( "hello world", ['a','o','w'], 5 );
>> 6
mIndexOf( "hello world", ['a','o','w'], 7 );
>> -1 
mIndexOf( "hello world", ['a','o','w','d'], 7 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 10 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 11 );
>> -1
Kent Fredric
źródło
Tylko komentarz na temat łatania małp - choć zdaję sobie sprawę z problemów - uważasz, że lepiej jest zanieczyszczać globalną przestrzeń nazw? To nie jest tak, że konflikty symboli w obu przypadkach nie mogą się zdarzyć i są zasadniczo refaktoryzowane / naprawiane w ten sam sposób, jeśli pojawi się problem.
Peter Bailey,
Cóż, muszę wyszukać \, a w niektórych przypadkach \ W i miałem nadzieję, że nie będę musiał wyliczać wszystkich możliwości.
Pat
BaileyP: możesz obejść ten problem bez globalnego zanieczyszczenia przestrzeni nazw, np .: patrz na przykład jQuery. użyj tego modelu. jeden obiekt do projektu, twoje rzeczy wchodzą do niego. Mootools pozostawił w moich ustach zły smak.
Kent Fredric,
również należy zauważyć, że nigdy nie koduję tak, jak tam napisałem. przykład został uproszczony ze względu na przypadki użycia.
Kent Fredric,