Gibt es eine Version von JavaScript String.indexOf (), die reguläre Ausdrücke zulässt?

214

Gibt es in Javascript ein Äquivalent zu String.indexOf (), das einen regulären Ausdruck anstelle eines Strings für den ersten ersten Parameter verwendet und dennoch einen zweiten Parameter zulässt?

Ich muss so etwas tun

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

und

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

Während String.search () einen regulären Ausdruck als Parameter verwendet, kann ich kein zweites Argument angeben!

Bearbeiten:
Dies stellte sich als schwieriger heraus, als ich ursprünglich gedacht hatte. Deshalb habe ich eine kleine Testfunktion geschrieben, um alle bereitgestellten Lösungen zu testen. Es wird davon ausgegangen, dass dem String-Objekt regexIndexOf und regexLastIndexOf hinzugefügt wurden.

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)]) ;
    }
}

und ich teste wie folgt, um sicherzustellen, dass mindestens für einen regulären Regexp das Ergebnis das gleiche ist, als ob wir indexOf verwendet hätten

// Suche das a unter den xes
test ('xxx');
Test ('Axx');
Test ('xax');
Test ('xxa');
Test ('Axa');
Test ('xaa');
Test ('aax');
Test ('aaa');

Klopfen
quelle
|Innen [ ]entspricht dem wörtlichen Zeichen |. Du hast es wahrscheinlich gemeint [abc].
Markus Jarderot
Ja, danke, Sie haben Recht, ich werde es beheben, aber der reguläre Ausdruck selbst ist irrelevant ...
Pat
Aktualisierte meine Antwort Pat, danke für jedes Feedback.
Jason Bunting
Ich fand einen einfacheren und effektiveren Ansatz darin, nur string.match (/ [AZ] /) zu verwenden. Wenn nicht viel vorhanden ist, gibt die Methode null zurück. Andernfalls erhalten Sie ein Objekt. Sie können den Index (/ [AZ] /). Abgleichen, um den Index des ersten Großbuchstabens zu erhalten
Syler

Antworten:

129

Wenn ich einige der bereits erwähnten Ansätze kombiniere (der indexOf ist offensichtlich ziemlich einfach), denke ich, dass dies die Funktionen sind, die den Trick machen:

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;
}

Das Ändern des integrierten String-Objekts würde natürlich für die meisten Menschen rote Fahnen setzen, aber dies kann ein Mal sein, wenn es keine so große Sache ist. sei dir dessen einfach bewusst.


UPDATE: Bearbeitet, regexLastIndexOf()so dass es lastIndexOf()jetzt zu imitieren scheint . Bitte lassen Sie mich wissen, ob und unter welchen Umständen dies immer noch fehlschlägt.


UPDATE: Besteht alle Tests, die in den Kommentaren auf dieser Seite gefunden wurden, und meine eigenen. Das heißt natürlich nicht, dass es kugelsicher ist. Jedes Feedback wird geschätzt.

Jason Bunting
quelle
Sie regexLastIndexOfgeben nur den Index der letzten nicht überlappenden Übereinstimmung zurück.
Markus Jarderot
Tut mir leid, kein RIESIGER Regex-Typ - können Sie mir ein Beispiel geben, das meine zum Scheitern bringen würde? Ich schätze es, mehr lernen zu können, aber Ihre Antwort hilft niemandem, der so unwissend ist wie ich. :)
Jason Bunting
Jason Ich habe gerade eine Funktion hinzugefügt, um die Frage zu testen. Dies schlägt (unter anderem) die folgende 'axx'.lastIndexOf (' a ', 2) fehl! =' axx'.regexLastIndexOf (/ a /, 2)
Pat
2
Ich denke, es ist effizienter zu verwenden, regex.lastIndex = result.index + 1;anstatt regex.lastIndex = ++nextStop;. Es wird hoffentlich viel schneller zum nächsten Spiel übergehen, ohne dass ein Ergebnis verloren geht.
Gedrox
1
Wenn Sie es lieber von npm abrufen möchten, sind diese beiden Util-Funktionen jetzt auf NPM verfügbar
Capaj
185

Instanzen des StringKonstruktors verfügen über eine .search()Methode, die ein RegExp akzeptiert und den Index der ersten Übereinstimmung zurückgibt.

Um die Suche von einer bestimmten Position aus zu starten (indem Sie den zweiten Parameter von vortäuschen .indexOf()), können sliceSie die ersten iZeichen deaktivieren :

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

Dadurch wird der Index jedoch in der kürzeren Zeichenfolge angezeigt (nachdem der erste Teil abgeschnitten wurde), sodass Sie die Länge des abgeschnittenen Teils ( i) zum zurückgegebenen Index hinzufügen möchten, wenn dies nicht der Fall ist -1. Dadurch erhalten Sie den Index in der ursprünglichen Zeichenfolge:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}
Glenn
quelle
1
aus der Frage: Während String.search () einen regulären Ausdruck als Parameter verwendet, kann ich kein zweites Argument angeben!
Pat
14
str.substr (i) .search (/ re /)
Glenn
6
Tolle Lösung, aber die Ausgabe ist etwas anders. indexOf gibt von Anfang an eine Zahl zurück (unabhängig vom Versatz), während dies die Position vom Versatz zurückgibt. Aus function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
Gründen der
39

Ich habe eine kurze Version für dich. Es funktioniert gut für mich!

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

Und wenn Sie eine Prototypversion wollen:

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;
}

BEARBEITEN : Wenn Sie Unterstützung für fromIndex hinzufügen möchten

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;
}

So einfach ist das:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);
pmrotule
quelle
Das ist eigentlich ein schöner Trick. Wäre großartig, wenn Sie es erweitern würden, um auch den startIndexParameter wie gewohnt zu übernehmen indeoxOfund zu lastIndexOftun.
Robert Koritnik
@RobertKoritnik - Ich habe meine Antwort auf Support startIndex(oder fromIndex) bearbeitet . Ich hoffe es hilft!
pmrotule
lastIndexOfRegexsollte auch den Wert von fromIndexzum Ergebnis zurückaddieren.
Peter
Ihr Algorithmus wird im folgenden Szenario aufgelöst: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Das Ergebnis ist 1, wenn es 7 sein sollte, da indexOf zum ersten Mal nach dem "Romeo" sucht, unabhängig davon, ob es am Anfang eines Wortes steht oder nicht.
KorelK
13

Verwenden:

str.search(regex)

Siehe die Dokumentation hier.

rmg.n3t
quelle
11
@ Ozze: Nein, nicht wirklich. Es ist im Grunde Glenns Antwort (mit ~ 150 Upvotes), außer dass sie keinerlei Erklärung hat, keine andere Startposition unterstützt als 0und sieben Jahre später veröffentlicht wurde.
ccjmne
7

Basierend auf der Antwort von BaileyP. Der Hauptunterschied besteht darin, dass diese Methoden zurückgegeben werden, -1wenn das Muster nicht übereinstimmt.

Edit: Dank der Antwort von Jason Bunting habe ich eine Idee. Warum nicht die .lastIndexEigenschaft des regulären Ausdrucks ändern ? Dies funktioniert jedoch nur für Muster mit dem globalen Flag ( /g).

Bearbeiten: Aktualisiert, um die Testfälle zu bestehen.

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
quelle
Dies scheint das vielversprechendste zu sein (nach ein paar Sytax-Korrekturen) :-) Nur ein paar Tests unter Randbedingungen fehlgeschlagen. Dinge wie 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Ich prüfe, ob ich diese Fälle beheben kann
Pat
6

Sie könnten substr verwenden.

str.substr(i).match(/[abc]/);
Andru Luvisi
quelle
Aus dem bekannten JavaScript-Buch von O'Reilly: "substr wurde von ECMAScript nicht standardisiert und ist daher veraltet." Aber ich mag die Grundidee hinter dem, worauf du hinaus willst.
Jason Bunting
1
Das ist kein Problem. Wenn Sie WIRKLICH besorgt sind, verwenden Sie stattdessen String.substring () - Sie müssen nur ein bisschen anders rechnen. Außerdem sollte JavaScript nicht zu 100% der übergeordneten Sprache verpflichtet sein.
Peter Bailey
Dies ist kein Problem. Wenn Sie Ihren Code für eine Implementierung ausführen, die substr nicht implementiert, weil sie die ECMAScript-Standards einhalten möchten, treten Probleme auf. Zugegeben, es ist nicht so schwer, es durch Teilzeichenfolgen zu ersetzen, aber es ist gut, sich dessen bewusst zu sein.
Jason Bunting
1
Sobald Sie Probleme haben, haben Sie sehr, sehr einfache Lösungen. Ich denke, die Kommentare sind vernünftig, aber die Abstimmung war pedantisch.
VoronoiPotato
Könnten Sie bitte Ihre Antwort bearbeiten, um einen funktionierenden Demo-Code bereitzustellen?
vsync
5

RexExpInstanzen haben bereits eine lastIndex- Eigenschaft (wenn sie global sind). Ich kopiere also den regulären Ausdruck, ändere ihn leicht, um ihn unseren Zwecken anzupassen, setzeexec ihn in die Zeichenfolge und schaue auf die lastIndex. Dies ist unweigerlich schneller als das Schleifen der Zeichenfolge. (Sie haben genug Beispiele, wie Sie dies auf den String-Prototyp setzen können, oder?)

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

Sie können die Funktionen auch als Prototyp für das RegExp-Objekt erstellen:

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

Eine kurze Erklärung, wie ich das ändere RegExp: Denn indexOfich muss nur sicherstellen, dass das globale Flag gesetzt ist. Für lastIndexOfof verwende ich einen negativen Look-Ahead, um das letzte Vorkommen zu finden, es sei denn, das RegExpstimmte bereits am Ende der Zeichenfolge überein.

Prestaul
quelle
4

Es ist nicht nativ, aber Sie können diese Funktionalität sicherlich hinzufügen

<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>

Ich habe diese Methoden nicht vollständig getestet, aber sie scheinen bisher zu funktionieren.

Peter Bailey
quelle
Aktualisiert, um diese Fälle zu behandeln
Peter Bailey
Jedes Mal, wenn ich diese Antwort akzeptiere, finde ich einen neuen Fall! Diese ergeben unterschiedliche Ergebnisse! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat
Nun, natürlich sind sie - str.lastIndexOf übt einen Zwang auf das Muster aus - und konvertiert es in einen String. Die Zeichenfolge "/ [d] /" wird mit Sicherheit nicht in der Eingabe gefunden, daher ist die zurückgegebene -1 tatsächlich korrekt.
Peter Bailey
Verstanden. Nachdem ich die Spezifikation auf String.lastIndexOf () gelesen hatte, habe ich nur falsch verstanden, wie dieses Argument funktioniert. Diese neue Version sollte damit umgehen.
Peter Bailey
Etwas stimmt immer noch nicht, aber es wird zu spät ... Ich werde versuchen, einen Testfall zu bekommen und ihn vielleicht am Morgen zu reparieren. Entschuldigung für die bisherigen Probleme.
Pat
2

Nachdem alle vorgeschlagenen Lösungen meine Tests auf die eine oder andere Weise nicht bestanden haben (bearbeiten: Einige wurden aktualisiert, um die Tests zu bestehen, nachdem ich dies geschrieben habe), fand ich die Mozilla-Implementierung für Array.indexOf und Array.lastIndexOf

Ich habe diese verwendet, um meine Version von String.prototype.regexIndexOf und String.prototype.regexLastIndexOf wie folgt zu implementieren:

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;
  };

Sie scheinen die Testfunktionen zu bestehen, die ich in der Frage angegeben habe.

Natürlich funktionieren sie nur, wenn der reguläre Ausdruck mit einem Zeichen übereinstimmt, aber das reicht für meinen Zweck aus, da ich ihn für Dinge wie ([abc], \ s, \ W, \ D) verwenden werde.

Ich werde die Frage weiter überwachen, falls jemand eine bessere / schnellere / sauberere / allgemeinere Implementierung bereitstellt, die mit jedem regulären Ausdruck funktioniert.

Klopfen
quelle
Wow, das ist ein langer Code. Bitte überprüfen Sie meine aktualisierte Antwort und geben Sie Feedback. Vielen Dank.
Jason Bunting
Diese Implementierung zielt auf absolute Kompatibilität mit lastIndexOf in Firefox und der SpiderMonkey-JavaScript-Engine ab, auch in einigen Fällen, bei denen es sich möglicherweise um Randfälle handelt. [...] In realen Anwendungen können Sie möglicherweise mit weniger kompliziertem Code rechnen, wenn Sie diese Fälle ignorieren.
Pat
Bilden Sie die Mozilla-Seite :-) Ich habe gerade den Code genommen und zwei Zeilen geändert, wobei alle Randfälle übrig geblieben sind. Da einige der anderen Antworten aktualisiert wurden, um die Tests zu bestehen, werde ich versuchen, sie zu vergleichen und die effizientesten zu akzeptieren. Wenn ich Zeit habe, das Thema erneut zu behandeln.
Pat
Ich habe meine Lösung aktualisiert und freue mich über Feedback oder Dinge, die dazu führen, dass sie fehlschlägt. Ich habe eine Änderung vorgenommen, um das überlappende Problem zu beheben, auf das MizardX hingewiesen hat (hoffentlich!)
Jason Bunting
2

Ich brauchte eine regexIndexOfFunktion auch für ein Array, also habe ich selbst eine programmiert. Ich bezweifle jedoch, dass es optimiert ist, aber ich denke, es sollte richtig funktionieren.

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
quelle
1

In bestimmten einfachen Fällen können Sie Ihre Rückwärtssuche mithilfe von split vereinfachen.

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

Dies hat einige schwerwiegende Probleme:

  1. Überlappende Übereinstimmungen werden nicht angezeigt
  2. Der zurückgegebene Index bezieht sich eher auf das Ende des Spiels als auf den Anfang (in Ordnung, wenn Ihr regulärer Ausdruck eine Konstante ist).

Aber auf der positiven Seite ist es viel weniger Code. Für einen regulären Ausdruck mit konstanter Länge, der sich nicht überlappen kann (wie /\s\w/zum Auffinden von Wortgrenzen), ist dies gut genug.

amwinter
quelle
0

Bei Daten mit geringen Übereinstimmungen ist die Verwendung von string.search in allen Browsern am schnellsten. Bei jeder Iteration wird eine Zeichenfolge neu aufgeteilt:

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;
}

Für dichte Daten habe ich das gemacht. Es ist komplex im Vergleich zur Ausführungsmethode, aber für dichte Daten ist es 2-10x schneller als jede andere Methode, die ich ausprobiert habe, und ungefähr 100x schneller als die akzeptierte Lösung. Die Hauptpunkte sind:

  1. Es ruft exec für den einmal übergebenen regulären Ausdruck auf, um zu überprüfen, ob eine Übereinstimmung vorliegt, oder um vorzeitig zu beenden. Ich mache dies mit (? = In einer ähnlichen Methode, aber im IE ist die Überprüfung mit exec dramatisch schneller.
  2. Es erstellt und speichert einen modifizierten regulären Ausdruck im Format '(r). (?!. ? r) '
  3. Der neue reguläre Ausdruck wird ausgeführt und die Ergebnisse von diesem oder dem ersten Exec werden zurückgegeben.

    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 von Methoden

Ich verstehe den Zweck der Tests oben nicht. Situationen, die eine Regex erfordern, können nicht mit einem Aufruf von indexOf verglichen werden, was meiner Meinung nach der Grund ist, die Methode überhaupt erst zu entwickeln. Um den Test zu bestehen, ist es sinnvoller, 'xxx + (?! X)' zu verwenden, als die Art und Weise anzupassen, in der der Regex iteriert.

npjohns
quelle
0

Der letzte Index von Jason Bunting funktioniert nicht. Meins ist nicht optimal, aber es funktioniert.

//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
quelle
Können Sie einen Test bereitstellen, bei dem mein Test fehlschlägt? Wenn Sie festgestellt haben, dass es nicht funktioniert, geben Sie einen Testfall an. Warum sagen Sie einfach "es funktioniert nicht" und stellen Sie eine nicht optimale Lösung bereit?
Jason Bunting
Hallo Junge. Du hast vollkommen recht. Ich hätte ein Beispiel geben sollen. Leider bin ich vor Monaten von diesem Code weitergegangen und habe keine Ahnung, was der Fehlerfall war. : - /
Eli
Nun, so ist das Leben. :)
Jason Bunting
0

Es gibt noch keine nativen Methoden, die die angeforderte Aufgabe ausführen.

Hier ist der Code, den ich verwende. Es ahmt das Verhalten von String.prototype.indexOf und String.prototype.lastIndexOf nach Methoden , akzeptiert jedoch zusätzlich zu einer Zeichenfolge, die den zu suchenden Wert darstellt, auch RegExp als .

Ja, es dauert ziemlich lange, bis eine Antwort vorliegt, da versucht wird, die aktuellen Standards so genau wie möglich zu befolgen, und natürlich eine angemessene Menge an JSDOC enthält Kommentaren enthält. Einmal minimiert, ist der Code jedoch nur 2,27 KB groß und einmal für die Übertragung komprimiert, sind es nur 1023 Bytes.

Die 2 Methoden, die dadurch hinzugefügt werdenString.prototype (unter Verwendung von Object.defineProperty , sofern verfügbar), sind:

  1. searchOf
  2. searchLastOf

Es besteht alle Tests, die das OP veröffentlicht hat, und außerdem habe ich die Routinen im täglichen Gebrauch ziemlich gründlich getestet und versucht, sicherzustellen, dass sie in mehreren Umgebungen funktionieren, aber Feedback / Probleme sind immer willkommen.

/*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
quelle
0

Wenn Sie nach einer sehr einfachen lastIndex-Suche mit RegExp suchen und es Ihnen egal ist, ob sie lastIndexOf bis ins letzte Detail nachahmt, kann dies Ihre Aufmerksamkeit erregen.

Ich kehre einfach die Zeichenfolge um und subtrahiere den ersten Vorkommensindex von der Länge - 1. Er besteht meinen Test, aber ich denke, dass bei langen Zeichenfolgen ein Leistungsproblem auftreten kann.

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
quelle
0

Ich habe verwendet, String.prototype.match(regex)was ein String-Array aller gefundenen Übereinstimmungen der regexin der String angegebenen Werte zurückgibt (weitere Informationen siehe hier ):

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
quelle
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
}

Verwenden Sie reguläre Standardausdrücke:

var re  = new RegExp("^ab");  // At front
var re  = new RegExp("ab$");  // At end
var re  = new RegExp("ab(c|d)");  // abc or abd
user984003
quelle
-2

Nun, da Sie nur versuchen, die Position eines Charakters zu erreichen , ist Regex möglicherweise übertrieben.

Ich nehme an, alles, was Sie wollen, ist, anstatt "zuerst diesen Charakter zu finden", nur den ersten dieser Charaktere zu finden.

Dies ist natürlich die einfache Antwort, macht aber das, was Ihre Frage vorsieht, allerdings ohne den Regex-Teil (weil Sie nicht geklärt haben, warum es speziell ein Regex sein musste).

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
quelle
Nur ein Kommentar zum Affen-Patching - obwohl ich mir seiner Probleme bewusst bin - denken Sie, dass es besser ist, den globalen Namespace zu verschmutzen? Es ist nicht so, dass Symbolkonflikte in BEIDEN Fällen nicht auftreten können und im Grunde genommen auf die gleiche Weise überarbeitet / repariert werden, falls ein Problem auftritt.
Peter Bailey
Nun, ich musste nach \ s und in einigen Fällen nach \ W suchen und hoffte, dass ich nicht alle Möglichkeiten aufzählen musste.
Pat
BaileyP: Sie können dieses Problem ohne globale Verschmutzung des Namespace umgehen, z. B.: Siehe jQuery zum Beispiel. Verwenden Sie dieses Modell. ein Objekt für das Projekt, Ihre Sachen gehen hinein. Mootools hinterließen einen schlechten Geschmack in meinem Mund.
Kent Fredric
Zu beachten ist auch, dass ich nie so codiere, wie ich es dort geschrieben habe. Das Beispiel wurde aus Anwendungsfallgründen vereinfacht.
Kent Fredric