Existe-t-il une version de String.indexOf () de JavaScript qui autorise les expressions régulières?

StackOverflow https://stackoverflow.com/questions/273789

  •  07-07-2019
  •  | 
  •  

Question

En javascript, existe-t-il un équivalent de String.indexOf () qui prend une expression régulière au lieu d'une chaîne pour le premier premier paramètre tout en permettant un second paramètre?

Je dois faire quelque chose comme

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

et

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

Bien que String.search () prenne une expression rationnelle en paramètre, il ne me permet pas de spécifier un deuxième argument!

Modifier:
Cela s’est avéré plus difficile que je ne le pensais au départ. J’ai donc écrit une petite fonction de test pour tester toutes les solutions fournies ... cela suppose que regexIndexOf et regexLastIndexOf ont été ajoutés à l’objet 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)]) ;
    }
}

et je teste comme suit pour m'assurer qu'au moins pour une expression rationnelle de caractères, le résultat est le même que si nous utilisions indexOf

// Cherchez le a parmi les xes
test ('xxx');
test ('axx');
test ('xax');
test ('xxa');
test ('axa');
test ('xaa');
test ('aax');
test ('aaa');

Était-ce utile?

La solution

En combinant quelques-unes des approches déjà mentionnées (l'indexOf est évidemment assez simple), je pense que ce sont les fonctions qui feront l'affaire:

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

Évidemment, la modification de l'objet String intégré déclencherait des signaux d'alarme pour la plupart des utilisateurs, mais il se peut que ce ne soit pas vraiment le cas. simplement en être conscient.

UPDATE: édité par regexLastIndexOf () de sorte que cela semble imiter lastIndexOf () maintenant. Faites-moi savoir s'il échoue toujours et dans quelles circonstances.

UPDATE: passe tous les tests trouvés dans les commentaires de cette page, ainsi que les miens. Bien sûr, cela ne signifie pas que c'est pare-balles. Tout commentaire apprécié.

Autres conseils

Les instances du constructeur String ont un .search () méthode qui accepte un RegExp et renvoie l'index de la première correspondance.

Pour lancer la recherche à partir d'une position particulière (en simulant le deuxième paramètre de .indexOf () ), vous pouvez couper du premier i . caractères:

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

Mais cela obtiendra l'index dans la chaîne la plus courte (après que la première partie a été coupée en tranches), vous voudrez donc ajouter la longueur de la partie coupée ( i ) à la valeur renvoyée. index si ce n'était pas -1 . Cela vous donnera l'index dans la chaîne d'origine:

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

J'ai une version courte pour vous. Cela fonctionne bien pour moi!

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

Et si vous voulez une version prototype:

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

MODIFIER : si vous souhaitez ajouter le support pour 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;
}

Pour l'utiliser, aussi simple que cela:

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

Utiliser:

str.search(regex)

Voir la documentation ici.

Vous pouvez utiliser substr.

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

Basé sur la réponse de BaileyP. La principale différence est que ces méthodes renvoient -1 si le modèle ne peut pas correspondre.

Modifier: Grâce à la réponse de Jason Bunting, j'ai eu une idée. Pourquoi ne pas modifier la propriété .lastIndex de la regex? Bien que cela ne fonctionne que pour les modèles avec l'indicateur global ( / g ).

Modifier: mis à jour pour réussir les tests.

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

Cela ne se fait pas de manière native, mais vous pouvez certainement ajouter cette fonctionnalité

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

Je n’ai pas testé complètement ces méthodes, mais elles semblent fonctionner jusqu’à présent.

Les instances

RexExp ont un lastIndex déjà (si elles sont globales) et ce que je fais est donc de copier l’expression rationnelle, de la modifier légèrement pour l’adapter à nos besoins, exec -le sur la chaîne et en regardant le lastIndex . Ce sera inévitablement plus rapide que la boucle sur la chaîne. (Vous avez suffisamment d'exemples sur la façon de placer ceci sur le prototype de chaîne, non?)

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

Vous pouvez également créer un prototype des fonctions sur l'objet 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

Une brève explication de la façon dont je modifie RegExp : Pour indexOf , je dois simplement m'assurer que l'indicateur global est défini. Pour lastIndexOf , j'utilise une prévision négative pour rechercher la dernière occurrence, à moins que RegExp ne corresponde déjà à la fin de la chaîne.

Après que toutes les solutions proposées aient échoué mes tests d’une manière ou d’une autre (modification: certaines ont été mises à jour pour réussir les tests après avoir écrit ceci), j’ai trouvé l’implémentation de mozilla pour Array.indexOf et Array.lastIndexOf

J'ai utilisé ceux-ci pour implémenter ma version de String.prototype.regexIndexOf et String.prototype.regexLastIndexOf comme suit:

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

Ils semblent réussir les fonctions de test que j'ai fournies dans la question.

De toute évidence, ils ne fonctionnent que si l'expression régulière correspond à un caractère, mais cela suffit pour mon propos, car je l'utiliserai pour des choses comme ([abc], \ s, \ W, \ D)

Je continuerai à surveiller la question au cas où quelqu'un fournirait une implémentation meilleure / plus rapide / plus propre / plus générique qui fonctionne pour toute expression régulière.

J'avais besoin d'une fonction regexIndexOf également pour un tableau, alors j'en ai programmé une moi-même. Cependant, je doute que cela soit optimisé, mais je suppose que cela devrait fonctionner correctement.

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

Dans certains cas simples, vous pouvez simplifier votre recherche en arrière en utilisant split.

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

Cela pose quelques problèmes graves:

  1. les correspondances ne s'afficheront pas
  2. l'index renvoyé correspond à la fin de la correspondance plutôt qu'au début (très bien si votre expression rationnelle est une constante)

Mais du côté positif, c'est beaucoup moins de code. Pour une expression rationnelle de longueur constante qui ne peut pas se chevaucher (comme / \ s \ w / pour trouver les limites de mots), cela suffit.

Pour les données avec des correspondances éparses, utiliser string.search est le plus rapide des navigateurs. Il refend une chaîne à chaque itération pour:

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

Pour les données denses, j'ai créé ceci. C'est complexe comparé à la méthode execute, mais pour les données denses, il est 2 à 10 fois plus rapide que toutes les autres méthodes que j'ai essayées et environ 100 fois plus rapidement que la solution acceptée. Les points principaux sont:

  1. Il appelle exec sur l’expression rationnelle transmise une fois pour vérifier la correspondance ou s’arrête tôt. Je fais cela en utilisant (? = Dans une méthode similaire, mais sous IE, vérifier avec exec est nettement plus rapide.
  2. Il construit et met en cache une expression rationnelle modifiée au format '(r). (?!. ? r)'
  3. La nouvelle expression rationnelle est exécutée et les résultats de cet exec ou du premier exec sont renvoyés;

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

jsPerf de méthodes

Je ne comprends pas le but des tests. Les situations qui nécessitent une expression rationnelle sont impossibles à comparer avec un appel à indexOf, ce qui, à mon avis, est l’intérêt de créer la méthode en premier lieu. Pour que le test réussisse, il est plus logique d'utiliser 'xxx + (?! X)' plutôt que de modifier la façon dont la regex itère.

+ 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 de méthodes

Je ne comprends pas le but des tests. Les situations qui nécessitent une expression rationnelle sont impossibles à comparer avec un appel à indexOf, ce qui, à mon avis, est l’intérêt de créer la méthode en premier lieu. Pour que le test réussisse, il est plus logique d'utiliser 'xxx + (?! X)' plutôt que de modifier la façon dont la regex itère.

Le dernier index de Jason Bunting ne fonctionne pas. Le mien n’est pas optimal, mais ça marche.

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

Il n'y a toujours aucune méthode native pour effectuer la tâche demandée.

Voici le code que j'utilise. Il imite le comportement de String.prototype.indexOf et String.prototype.lastIndexOf , mais méthodes ils acceptent également RegExp comme argument de recherche en plus d’une chaîne représentant la valeur à rechercher.

Oui, la réponse est assez longue car elle tente de respecter les normes en vigueur et contient bien sûr une quantité raisonnable de JSDOC commentaires. Cependant, une fois minifiés, le code n’est plus que de 2,27 Ko et une fois compressés pour la transmission, il ne reste plus que 1023 octets.

Les 2 méthodes ajoutées à String.prototype (avec Object.defineProperty , le cas échéant) sont les suivants:

  1. searchOf
  2. searchLastOf

Tous les tests publiés par le PO ont été testés. De plus, j’ai testé les routines de manière assez approfondie dans mon utilisation quotidienne et j’ai tenté de nous assurer qu’elles fonctionnent dans plusieurs environnements, mais les réactions / problèmes sont toujours les bienvenus.

/*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]*)
<pre id="out"></pre>

, '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'); }());
<*>

Si vous recherchez une recherche lastIndex très simple avec RegExp et ne vous inquiétez pas si elle imite lastIndexOf dans les moindres détails, cela peut attirer votre attention.

J'inverse simplement la chaîne et soustrais le premier indice d'occurrence à la longueur - 1. Il réussit mon test, mais je pense qu'un problème de performance pourrait survenir avec les chaînes longues.

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

Eh bien, si vous cherchez à faire correspondre la position d'un caractère , l'expression rationnelle est peut-être excessive.

Je suppose que tout ce que vous voulez, c’est au lieu de "trouver en premier ce caractère". , trouvez juste le premier de ces personnages.

Bien sûr, la réponse est simple, mais correspond à ce que votre question a l'intention de faire, bien que sans la partie regex (parce que vous n'avez pas expliqué en détail pourquoi cela devait être une 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top