Esiste una versione di String.indexOf () di JavaScript che consente espressioni regolari?


214

In javascript, esiste un equivalente di String.indexOf () che accetta un'espressione regolare anziché una stringa per il primo primo parametro pur consentendo comunque un secondo parametro?

Devo fare qualcosa del genere

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

e

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

Mentre String.search () accetta un regexp come parametro, non mi consente di specificare un secondo argomento!

Modifica:
Questo si è rivelato più difficile di quanto pensassi inizialmente, quindi ho scritto una piccola funzione di test per testare tutte le soluzioni fornite ... presuppone che regexIndexOf e regexLastIndexOf siano stati aggiunti all'oggetto String.

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

e sto testando come segue per assicurarmi che almeno per un carattere regexp, il risultato sia lo stesso di se avessimo usato indexOf

// Cerca la a tra i
test xes ('xxx');
Test ( 'axx');
Test ( 'xax');
Test ( 'xxa');
Test ( 'AXA');
Test ( 'xaa');
Test ( 'aax');
Test ( 'aaa');


|all'interno [ ]corrisponde al carattere letterale |. Probabilmente intendevi [abc].
Markus Jarderot,

si grazie hai ragione, lo aggiusterò ma la regexp stessa è irrilevante ...
Pat

Aggiornato la mia risposta Pat, grazie per qualsiasi feedback.
Jason Bunting,

Ho trovato un approccio più semplice ed efficace è usare solo string.match (/ [AZ] /). Se non c'è molto, il metodo restituisce null, altrimenti si ottiene un oggetto, si può fare match (/ [AZ] /). Index per ottenere l'indice della prima lettera maiuscola
Syler

Risposte:


129

Combinando alcuni degli approcci già menzionati (l'indiceOf è ovviamente piuttosto semplice), penso che queste siano le funzioni che faranno il trucco:

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

Ovviamente, la modifica dell'oggetto String incorporato invierebbe bandiere rosse per la maggior parte delle persone, ma questa potrebbe essere una volta in cui non è un grosso problema; semplicemente esserne consapevole.


AGGIORNAMENTO: modificato in regexLastIndexOf()modo da sembrare imitare lastIndexOf()ora. Per favore fatemi sapere se continua a fallire e in quali circostanze.


AGGIORNAMENTO: supera tutti i test trovati nei commenti su questa pagina e il mio. Naturalmente, ciò non significa che sia a prova di proiettile. Qualsiasi feedback apprezzato.


Il tuo regexLastIndexOfrestituisce solo l'indice dell'ultimo non sovrapposti partita.
Markus Jarderot,

Spiacente, non un ENORME ragazzo regex - puoi darmi un esempio che potrebbe far fallire il mio? Apprezzo essere in grado di saperne di più, ma la tua risposta non aiuta una persona ignorante come me. :)
Jason Bunting

Jason Ho appena aggiunto alcune funzioni per testare la domanda. questo non riesce (tra gli altri test) il seguente 'axx'.lastIndexOf (' a ', 2)! =' axx'.regexLastIndexOf (/ a /, 2)
Pat

2
Penso che sia più efficiente da usare regex.lastIndex = result.index + 1;invece di regex.lastIndex = ++nextStop;. Si procederà alla partita successiva molto più veloce speriamo senza perdere alcun risultato.
Gedrox,

1
Se preferisci estrarlo da npm, queste due funzioni di utilità sono ora su NPM come: npmjs.com/package/index-of-regex
Capaj

185

Le istanze del Stringcostruttore hanno un .search()metodo che accetta un RegExp e restituisce l'indice della prima corrispondenza.

Per iniziare la ricerca da una posizione particolare (falsificando il secondo parametro di .indexOf()) è possibile slicedisattivare i primi icaratteri:

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

Ma questo otterrà l'indice nella stringa più corta (dopo che la prima parte è stata tagliata) quindi ti consigliamo di aggiungere la lunghezza della parte tagliata ( i) all'indice restituito se non lo fosse -1. Questo ti darà l'indice nella stringa originale:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}

1
dalla domanda: Mentre String.search () accetta un regexp come parametro, non mi consente di specificare un secondo argomento!
Pat

14
str.substr (i) .search (/ re /)
Glenn,

6
Ottima soluzione, tuttavia l'output è leggermente diverso. indexOf restituirà un numero dall'inizio (indipendentemente dall'offset), mentre ciò restituirà la posizione dall'offset. Quindi, per parità, vorrai qualcosa di più simile a questo:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger,

39

Ho una versione corta per te. Funziona bene per me!

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

E se vuoi una versione prototipo:

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

EDIT : se si desidera aggiungere il supporto per 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;
}

Per usarlo, semplice come questo:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);

Questo è in realtà un bel trucco. Sarebbe fantastico se lo espandessi per prendere anche il startIndexparametro come al solito indeoxOfe lastIndexOffarlo.
Robert Koritnik,

@RobertKoritnik - Ho modificato la mia risposta per supportare startIndex(o fromIndex). Spero che sia d'aiuto!
pmrotule,

lastIndexOfRegexdovrebbe anche aggiungere di nuovo il valore fromIndexal risultato.
Peter,

Il tuo algoritmo verrà interrotto nel seguente scenario: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Il risultato sarà 1 quando dovrebbe essere 7, perché indexOf cercherà per la prima volta che appare "romeo", indipendentemente dal fatto che sia all'inizio di una parola o meno.
KorelK

13

Uso:

str.search(regex)

Vedi la documentazione qui.


11
@OZZIE: No, non proprio. Fondamentalmente è la risposta di Glenn (con ~ 150 voti), tranne per il fatto che non ha alcuna spiegazione , non supporta posizioni di partenza diverse da quelle 0, ed è stata pubblicata ... sette anni dopo.
ccjmne,

7

Basato sulla risposta di BaileyP. La differenza principale è che questi metodi ritornano -1se non è possibile abbinare il modello.

Modifica: grazie alla risposta di Jason Bunting ho avuto un'idea. Perché non modificare la .lastIndexproprietà di regex? Sebbene questo funzionerà solo per i modelli con la bandiera globale ( /g).

Modifica: aggiornato per superare i casi di test.

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

Questo sembra il più promettente finora (dopo alcune correzioni di sintassi) :-) Solo alcuni test falliti sulle condizioni del bordo. Cose come 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Sto cercando di capire se riesco a risolvere quei casi
Pat

6

È possibile utilizzare substr.

str.substr(i).match(/[abc]/);

Dal noto libro JavaScript pubblicato da O'Reilly: "substr non è stato standardizzato da ECMAScript ed è quindi deprecato". Ma mi piace l'idea di base dietro ciò a cui stai arrivando.
Jason Bunting,

1
Questo è un problema. Se sei DAVVERO preoccupato, usa String.substring () invece - devi solo fare un po 'di matematica in modo diverso. Inoltre, JavaScript non dovrebbe essere considerato al 100% nella sua lingua madre.
Peter Bailey,

Non è un problema: se il tuo codice viene eseguito su un'implementazione che non implementa il substr perché vogliono aderire agli standard ECMAScript, avrai dei problemi. Certo, sostituirlo con sottostringa non è così difficile da fare, ma è bene esserne consapevoli.
Jason Bunting

1
Nel momento in cui hai problemi, hai una soluzione molto semplice. Penso che i commenti siano sensati, ma il voto negativo è stato pedante.
VoronoiPotato,

Potresti modificare la tua risposta per fornire un codice demo funzionante?
vsync,

5

RexExple istanze hanno già una proprietà lastIndex (se sono globali) e quindi quello che sto facendo è copiare l'espressione regolare, modificarla leggermente per adattarla ai nostri scopi, execmetterla sulla stringa e guardare lastIndex. Questo sarà inevitabilmente più veloce del looping sulla stringa. (Hai abbastanza esempi di come inserirlo nel prototipo di stringa, giusto?)

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

È inoltre possibile prototipare le funzioni sull'oggetto 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

Una rapida spiegazione di come sto modificando RegExp: Perché indexOfdevo solo assicurarmi che la bandiera globale sia impostata. Per lastIndexOfdi sto usando uno sguardo negativo per trovare l'ultima occorrenza a meno che non RegExpfosse già corrispondente alla fine della stringa.


4

Non è nativo, ma è sicuramente possibile aggiungere questa funzionalità

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

Non ho testato completamente questi metodi, ma sembrano funzionare finora.


Aggiornato per gestire questi casi
Peter Bailey,

ogni volta che sto per accettare questa risposta trovo un nuovo caso! Questi danno risultati diversi! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat

bene, certo che lo sono - str.lastIndexOf scriverà la coercizione sul modello - convertendolo in una stringa. La stringa "/ [d] /" certamente non si trova nell'input, quindi il -1 restituito è effettivamente accurato.
Peter Bailey,

Fatto. Dopo aver letto le specifiche su String.lastIndexOf () - ho appena frainteso il funzionamento di tale argomento. Questa nuova versione dovrebbe gestirla.
Peter Bailey,

Qualcosa non va ancora bene, ma sta arrivando tardi ... Cercherò di ottenere un caso di prova e forse lo risolverò al mattino. Ci scusiamo per il disturbo finora.
Pat

2

Dopo che tutte le soluzioni proposte hanno fallito i miei test in un modo o nell'altro, (modifica: alcuni sono stati aggiornati per superare i test dopo averlo scritto) Ho trovato l'implementazione di mozilla per Array.indexOf e Array.lastIndexOf

Ho usato quelli per implementare la mia versione di String.prototype.regexIndexOf e String.prototype.regexLastIndexOf come segue:

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

Sembrano passare le funzioni di test fornite nella domanda.

Ovviamente funzionano solo se l'espressione regolare corrisponde a un personaggio, ma è abbastanza per il mio scopo poiché lo userò per cose come ([abc], \ s, \ W, \ D)

Continuerò a monitorare la domanda nel caso in cui qualcuno fornisca un'implementazione migliore / più veloce / più pulita / più generica che funzioni su qualsiasi espressione regolare.


Wow, è un po 'di codice. Controlla la mia risposta aggiornata e fornisci un feedback. Grazie.
Jason Bunting,

Questa implementazione mira alla totale compatibilità con lastIndexOf in Firefox e il motore JavaScript SpiderMonkey, inclusi in molti casi che sono probabilmente casi limite. [...] nelle applicazioni del mondo reale, potresti essere in grado di calcolare con un codice meno complicato se ignori quei casi.
Pat

Dalla pagina mozilla :-) Ho appena preso il codice e cambio due righe lasciando tutti i casi limite. Dato che un paio di altre risposte sono state aggiornate per superare i test, proverò a confrontarle e ad accettare le più efficaci. Quando ho tempo di rivisitare il problema.
Pat

Ho aggiornato la mia soluzione e apprezzo qualsiasi feedback o cose che ne facciano fallire. Ho apportato una modifica per risolvere il problema di sovrapposizione segnalato da MizardX (si spera!)
Jason Bunting,

2

Avevo bisogno di una regexIndexOffunzione anche per un array, quindi ne ho programmata una anch'io. Tuttavia, dubito che sia ottimizzato, ma suppongo che dovrebbe funzionare correttamente.

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

1

In alcuni casi semplici, è possibile semplificare la ricerca all'indietro utilizzando la divisione.

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

Questo ha alcuni gravi problemi:

  1. le partite sovrapposte non verranno visualizzate
  2. l'indice restituito è per la fine della partita anziché per l'inizio (va bene se la tua regex è una costante)

Ma il lato positivo è molto meno codice. Per una regex di lunghezza costante che non può sovrapporsi (come /\s\w/per trovare confini di parole), questo è abbastanza buono.


0

Per i dati con corrispondenze sparse, l'utilizzo di string.search è il più veloce tra i browser. Taglia nuovamente una stringa ogni iterazione per:

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

Per dati densi l'ho fatto. È complesso rispetto al metodo execute, ma per i dati densi è 2-10 volte più veloce di ogni altro metodo che ho provato e circa 100 volte più veloce della soluzione accettata. I punti principali sono:

  1. Chiama exec sul regex passato una volta per verificare che ci sia una corrispondenza o uscire presto. Lo faccio usando (? = In un metodo simile, ma su IE il controllo con exec è notevolmente più veloce.
  2. Costruisce e memorizza nella cache una regex modificata nel formato '(r). (?!. ? r) '
  3. Il nuovo regex viene eseguito e vengono restituiti i risultati di quel exec o del primo 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 di metodi

Non capisco lo scopo dei test in alto. Le situazioni che richiedono una regex sono impossibili da confrontare con una chiamata a indexOf, che credo sia il punto di fare il metodo in primo luogo. Per far passare il test, ha più senso usare 'xxx + (?! x)', piuttosto che regolare il modo in cui regex itera.


0

L'ultimo indice di Jason Bunting non funziona. Il mio non è ottimale, ma funziona.

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

Potete fornire un test che fa fallire il mio? Se hai scoperto che non funziona, fornisci un caso di prova, perché dire semplicemente "non funziona" e fornire una soluzione non ottimale?
Jason Bunting,

Ragazzo Hai perfettamente ragione. Avrei dovuto fornire un esempio. Purtroppo sono passato da questo codice mesi fa e non ho idea di quale fosse il caso fallito. : - /
Eli

bene, tale è la vita. :)
Jason Bunting il

0

Non esistono ancora metodi nativi che eseguono l'attività richiesta.

Ecco il codice che sto usando. Imita il comportamento dei metodi String.prototype.indexOf e String.prototype.lastIndexOf ma accetta anche un RegExp come argomento di ricerca oltre a una stringa che rappresenta il valore da cercare.

Sì, è abbastanza lungo quando arriva una risposta mentre cerca di seguire gli standard attuali il più vicino possibile e ovviamente contiene una quantità ragionevole di commenti JSDOC . Tuttavia, una volta minimizzato, il codice è solo 2,27 k e una volta compresso per la trasmissione è solo 1023 byte.

I 2 metodi a cui questo si aggiunge String.prototype(usando Object.defineProperty dove disponibile) sono:

  1. searchOf
  2. searchLastOf

Supera tutti i test pubblicati dall'OP e inoltre ho testato le routine abbastanza accuratamente nel mio uso quotidiano e ho cercato di essere sicuro che funzionino in più ambienti, ma feedback / problemi sono sempre ben accetti.

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


0

Se stai cercando una ricerca lastIndex molto semplice con RegExp e non ti importa se imita lastIndexO fino all'ultimo dettaglio, questo potrebbe attirare la tua attenzione.

Devo semplicemente invertire la stringa e sottrarre il primo indice di occorrenza dalla lunghezza - 1. Capita di superare il mio test, ma penso che potrebbe sorgere un problema di prestazioni con stringhe lunghe.

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

0

Ho usato il String.prototype.match(regex)quale restituisce un array di stringhe di tutte le corrispondenze trovate del dato regexnella stringa (maggiori informazioni vedi qui ):

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


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
}

Usa espressioni regolari standard:

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

-2

Bene, poiché stai solo cercando di abbinare la posizione di un personaggio , regex è probabilmente eccessivo.

Presumo che tutto ciò che vuoi sia, invece di "trovare prima questo personaggio", trova solo il primo di questi personaggi.

Questa ovviamente è la risposta semplice, ma fa quello che la tua domanda si propone di fare, anche se senza la parte regex (perché non hai chiarito perché in particolare doveva essere una regex)

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

Solo un commento sul patching delle scimmie - mentre sono consapevole dei suoi problemi - pensi che inquinare lo spazio dei nomi globale sia meglio? Non è come se non potessero verificarsi conflitti di simboli in ENTRAMBI i casi, e in pratica vengono rifattorizzati / riparati allo stesso modo in caso di problemi.
Peter Bailey,

Beh, ho bisogno di cercare \ s e in alcuni casi \ W e speravo di non dover elencare tutte le possibilità.
Pat

BaileyP: puoi aggirare questo problema senza inquinamento dello spazio dei nomi globale, ad esempio: vedi jQuery per esempio. usa quel modello. un oggetto per il progetto, le tue cose vanno al suo interno. I mootools hanno lasciato un cattivo sapore in bocca.
Kent Fredric,

anche da notare non ho mai codice come ho scritto lì. l'esempio è stato semplificato per motivi di casi d'uso.
Kent Fredric,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.