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

214

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 autorisant un deuxième paramètre?

Je dois faire quelque chose comme

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

et

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

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

Edit:
Cela s'est avéré plus difficile que je ne le pensais à l'origine, 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ère, le résultat est le même que si nous utilisions indexOf

// Recherchez le a parmi le
test xes ('xxx');
test («axx»);
test («xax»);
test («xxa»);
test («axa»);
test («xaa»);
test («aax»);
test («aaa»);

Tapoter
la source
|l'intérieur [ ]correspond au caractère littéral |. Vous vouliez probablement dire [abc].
Markus Jarderot
oui merci vous avez raison, je vais le réparer mais le regexp lui-même n'a pas d'importance ...
Pat
Mis à jour ma réponse Pat, merci pour tout commentaire.
Jason Bunting
J'ai trouvé qu'une approche plus simple et efficace consiste simplement à utiliser string.match (/ [AZ] /). S'il n'y a pas grand-chose, la méthode renvoie null, sinon vous obtenez un objet, vous pouvez faire correspondre (/ [AZ] /). Index pour obtenir l'index de la première lettre majuscule
Syler

Réponses:

129

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é enverrait des drapeaux rouges pour la plupart des gens, mais cela peut être une fois où ce n'est pas si grave; soyez simplement au courant.


MISE À JOUR: Modifié de regexLastIndexOf()sorte que cela semble imiter lastIndexOf()maintenant. Veuillez me faire savoir si cela échoue toujours et dans quelles circonstances.


MISE À JOUR: passe tous les tests trouvés dans les commentaires sur cette page, et le mien. Bien sûr, cela ne signifie pas qu'il est à l'épreuve des balles. Toute rétroaction appréciée.

Jason Bunting
la source
Votre regexLastIndexOfne renverra que l'index de la dernière correspondance sans chevauchement .
Markus Jarderot
Désolé, pas un énorme gars regex - pouvez-vous me donner un exemple qui ferait échouer le mien? J'apprécie de pouvoir en savoir plus, mais votre réponse n'aide pas quelqu'un d'aussi ignorant que moi. :)
Jason Bunting
Jason Je viens d'ajouter une fonction à tester dans la question. cela échoue (entre autres tests) au suivant 'axx'.lastIndexOf (' a ', 2)! =' axx'.regexLastIndexOf (/ a /, 2)
Pat
2
Je pense qu'il est plus efficace d'utiliser regex.lastIndex = result.index + 1;au lieu de regex.lastIndex = ++nextStop;. Il se passera au prochain match beaucoup plus rapidement, espérons-le, sans perdre aucun résultat.
Gedrox
1
Si vous préférez le retirer de npm, ces deux fonctions utilitaires sont maintenant sur NPM en tant que: npmjs.com/package/index-of-regex
Capaj
185

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

Pour démarrer la recherche à partir d'une position particulière (en simulant le deuxième paramètre de .indexOf()), vous pouvez slicedésactiver les premiers icaractè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), vous voudrez donc ajouter la longueur de la partie coupée ( i) à l'index retourné s'il ne l'é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;
}
Glenn
la source
1
de la question: Alors que String.search () prend une expression rationnelle comme paramètre, il ne me permet pas de spécifier un deuxième argument!
Pat
14
str.substr (i) .search (/ re /)
Glenn
6
Excellente solution, mais la sortie est un peu différente. indexOf renverra un nombre depuis le début (quel que soit le décalage), tandis que cela renverra la position du décalage. Donc, pour la parité, vous voudrez quelque chose de plus comme ceci:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger
39

J'ai une version courte pour vous. Ça marche 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;
}

EDIT : si vous souhaitez ajouter la prise en charge de 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);
pmrotule
la source
C'est en fait une bonne astuce. Ce serait génial si vous le développiez pour prendre également le startIndexparamètre comme d'habitude indeoxOfet le lastIndexOffaire.
Robert Koritnik
@RobertKoritnik - J'ai modifié ma réponse au support startIndex(ou fromIndex). J'espère que ça aide!
pmrotule
lastIndexOfRegexdevrait également ajouter la valeur de fromIndexau résultat.
Peter
Votre algorithme sera rompu dans le scénario suivant: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));Le résultat sera 1 alors qu'il devrait être 7, car indexOf recherchera pour la première fois le "romeo" apparaît, peu importe qu'il soit au début d'un mot ou non.
KorelK
13

Utilisation:

str.search(regex)

Voir la documentation ici.

rmg.n3t
la source
11
@OZZIE: Non, pas vraiment. C'est essentiellement la réponse de Glenn (avec ~ 150 votes positifs), sauf qu'elle n'a aucune explication , ne prend pas en charge la position de départ autre que 0, et a été publiée ... sept ans plus tard.
ccjmne
7

Basé sur la réponse de BaileyP. La principale différence est que ces méthodes retournent -1si le modèle ne peut pas être mis en correspondance.

Edit: Grâce à la réponse de Jason Bunting, j'ai eu une idée. Pourquoi ne pas modifier la .lastIndexpropriété de l'expression régulière? Bien que cela ne fonctionne que pour les modèles avec le drapeau global ( /g).

Edit: mis à jour pour passer les cas de 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;
}
Markus Jarderot
la source
Cela semble le plus prometteur à ce jour (après quelques corrections de sytax) :-) Échec de quelques tests sur les conditions de bord. Des choses comme 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Je regarde dedans pour voir si je peux réparer ces cas
Pat
6

Vous pouvez utiliser substr.

str.substr(i).match(/[abc]/);
Andru Luvisi
la source
D'après le livre JavaScript bien connu publié par O'Reilly: "substr n'a pas été normalisé par ECMAScript et est donc obsolète." Mais j'aime l'idée de base derrière laquelle vous voulez en venir.
Jason Bunting
1
Ce n'est pas un problème. Si cela vous inquiète vraiment, utilisez plutôt String.substring () - il vous suffit de faire les calculs un peu différemment. En outre, JavaScript ne doit pas être redevable à 100% de son langage parent.
Peter Bailey
Ce n'est pas un problème - si vous exécutez votre code sur une implémentation qui n'implémente pas substr parce qu'ils veulent adhérer aux normes ECMAScript, vous allez avoir des problèmes. Certes, le remplacer par une sous-chaîne n'est pas si difficile à faire, mais il est bon d'en être conscient.
Jason Bunting
1
Au moment où vous avez des problèmes, vous avez des solutions très très simples. Je pense que les commentaires sont sensés, mais le vote négatif a été pédant.
VoronoiPotato
Pourriez-vous modifier votre réponse pour fournir un code de démonstration fonctionnel?
vsync
5

RexExples instances ont déjà une propriété lastIndex (si elles sont globales) et donc ce que je fais est de copier l'expression régulière, de la modifier légèrement pour l'adapter à nos besoins, de la execplacer sur la chaîne et de regarder le lastIndex. Ce sera inévitablement plus rapide que de boucler sur la chaîne. (Vous avez suffisamment d'exemples sur la façon de mettre cela 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 prototyper les 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 explication rapide de la façon dont je modifie le RegExp: car indexOfje dois simplement m'assurer que le drapeau global est défini. Pour, lastIndexOfj'utilise une anticipation négative pour trouver la dernière occurrence, sauf si le RegExpcorrespondait déjà à la fin de la chaîne.

Prestaul
la source
4

Ce n'est pas natif, 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.

Peter Bailey
la source
Mis à jour pour gérer ces cas
Peter Bailey
chaque fois que je suis sur le point d'accepter cette réponse, je trouve un nouveau cas! Cela donne des résultats différents! alerte ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat
bien sûr, ils le sont - str.lastIndexOf fera de la coercition de type sur le modèle - le convertissant en une chaîne. La chaîne "/ [d] /" est très certainement introuvable dans l'entrée, donc le -1 renvoyé est en fait précis.
Peter Bailey
Je l'ai. Après avoir lu la spécification sur String.lastIndexOf () - j'ai juste mal compris comment cet argument fonctionnait. Cette nouvelle version devrait le gérer.
Peter Bailey
Quelque chose ne va toujours pas, mais il se fait trop tard ... Je vais essayer d'avoir un cas de test, et peut-être le réparer le matin. Désolé pour le problème jusqu'à présent.
Pat
2

Après que toutes les solutions proposées ont échoué mes tests d'une manière ou d'une autre, (modifier: certaines ont été mises à jour pour passer les tests après avoir écrit ceci) J'ai trouvé l'implémentation 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 passer 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 objectif car je vais l'utiliser 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 sur n'importe quelle expression régulière.

Tapoter
la source
Wow, c'est un long morceau de code. Veuillez vérifier ma réponse mise à jour et fournir des commentaires. Merci.
Jason Bunting
Cette implémentation vise une compatibilité absolue avec lastIndexOf dans Firefox et le moteur JavaScript SpiderMonkey, y compris dans plusieurs cas qui sont sans doute des cas marginaux. [...] dans les applications du monde réel, vous pourrez peut-être calculer à partir de code moins compliqué si vous ignorez ces cas.
Pat
Former la page mozilla :-) Je viens de prendre le code et changer deux lignes en laissant tous les cas de bord. Étant donné que quelques-unes des autres réponses ont été mises à jour pour réussir les tests, je vais essayer de les comparer et accepter les plus efficaces. Quand j'ai le temps de revoir la question.
Pat
J'ai mis à jour ma solution et j'apprécie tous les commentaires ou les choses qui provoquent son échec. J'ai apporté une modification pour résoudre le problème de chevauchement signalé par MizardX (espérons-le!)
Jason Bunting
2

J'avais également besoin d'une regexIndexOffonction pour un tableau, donc j'en ai programmé une moi-même. Cependant, je doute qu'il 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);
jakov
la source
1

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

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

Cela a quelques problèmes graves:

  1. les correspondances qui se chevauchent n'apparaissent pas
  2. l'index renvoyé correspond à la fin de la correspondance plutôt qu'au début (très bien si votre expression régulière est une constante)

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

amwinter
la source
0

Pour les données avec des correspondances clairsemées, l'utilisation de string.search est la plus rapide parmi les navigateurs. Il retaille 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, je l'ai fait. C'est complexe par rapport à la méthode d'exécution, mais pour les données denses, c'est 2 à 10 fois plus rapide que toutes les autres méthodes que j'ai essayées et environ 100 fois plus rapide que la solution acceptée. Les points principaux sont:

  1. Il appelle exec sur l'expression régulière passée une fois pour vérifier qu'il existe une correspondance ou quitter prématurément. Je fais cela en utilisant (? = Dans une méthode similaire, mais sur IE, la vérification avec exec est considérablement plus rapide.
  2. Il construit et met en cache une expression régulière modifiée au format '(r). (?!. ? r) '
  3. La nouvelle expression régulière est exécutée et les résultats de cet exécutable ou du premier exécutable 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 + '$' + 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 des méthodes

Je ne comprends pas le but des tests en haut. Les situations qui nécessitent une expression régulière sont impossibles à comparer avec un appel à indexOf, ce qui, à mon avis, est le point de faire la méthode en premier lieu. Pour réussir le test, il est plus logique d'utiliser «xxx + (?! X)» que d'ajuster la manière dont l'expression régulière s'exprime.

npjohns
la source
0

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;
}
Eli
la source
Pouvez-vous fournir un test qui fait échouer le mien? Si vous avez trouvé que cela ne fonctionne pas, fournissez un scénario de test, pourquoi dire simplement "cela ne fonctionne pas" et fournir une solution non optimale en place?
Jason Bunting
Hoo boy. Vous avez tout à fait raison. J'aurais dû fournir un exemple. Malheureusement, je suis passé de ce code il y a des mois et je n'ai aucune idée du cas d'échec. : - /
Eli
enfin, telle est la vie. :)
Jason Bunting
0

Il n'y a toujours pas de méthodes natives qui effectuent la tâche demandée.

Voici le code que j'utilise. Il imite le comportement des méthodes String.prototype.indexOf et String.prototype.lastIndexOf mais ils acceptent également un 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 essaie de suivre les normes actuelles aussi près que possible et contient bien sûr un nombre raisonnable de commentaires JSDOC . Cependant, une fois minifié, le code n'est que de 2,27 Ko et une fois compressé pour la transmission, il n'est que de 1023 octets.

Les 2 méthodes auxquelles cela s'ajoute String.prototype(en utilisant Object.defineProperty lorsque disponible) sont:

  1. searchOf
  2. searchLastOf

Il passe tous les tests publiés par l'OP et, en plus, j'ai testé les routines de manière assez approfondie dans mon utilisation quotidienne, et j'ai essayé de m'assurer qu'elles fonctionnent dans plusieurs environnements, mais les commentaires / 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]*)$', '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
la source
0

Si vous recherchez une recherche lastIndex très simple avec RegExp et ne vous souciez 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 de la longueur - 1. Il se trouve que je réussis mon test, mais je pense qu'il pourrait y avoir un problème de performances 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;
};
Reijo
la source
0

J'ai utilisé String.prototype.match(regex)qui retourne un tableau de chaînes de toutes les correspondances trouvées de la donnée regexdans la chaîne (plus d'informations voir ici ):

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
la source
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
}

Utilisez des expressions régulières 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
user984003
la source
-2

Eh bien, comme vous cherchez simplement à faire correspondre la position d'un personnage , l'expression régulière est probablement exagérée.

Je suppose que tout ce que vous voulez c'est, au lieu de "trouver en premier de ce personnage", il vous suffit de trouver en premier de ces personnages.

Bien sûr, c'est la réponse simple, mais fait ce que votre question vise à faire, bien que sans la partie regex (parce que vous n'avez pas clarifié pourquoi spécifiquement il 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
Kent Fredric
la source
Juste un commentaire sur le patch de singe - alors que je suis conscient de ses problèmes - vous pensez qu'il est préférable de polluer l'espace de noms global? Ce n'est pas comme si les conflits de symboles dans les DEUX cas ne pouvaient pas se produire, et sont fondamentalement refactorisés / réparés de la même manière en cas de problème.
Peter Bailey
Eh bien, je dois rechercher \ s et dans certains cas \ W et j'espérais ne pas avoir à énumérer toutes les possibilités.
Pat
BaileyP: vous pouvez contourner ce problème sans pollution globale de l'espace de noms, c'est-à-dire: voir jQuery par exemple. utilisez ce modèle. un seul objet pour le projet, vos affaires y entrent. Mootools a laissé un mauvais goût dans ma bouche.
Kent Fredric
à noter également que je n'ai jamais codé comme j'y ai écrit. l'exemple a été simplifié pour des raisons de cas d'utilisation.
Kent Fredric