¿Existe una versión de String.indexOf () de JavaScript que permita expresiones regulares?

214

En javascript, ¿hay un equivalente de String.indexOf () que toma una expresión regular en lugar de una cadena para el primer primer parámetro mientras permite un segundo parámetro?

Necesito hacer algo como

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

y

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

Si bien String.search () toma una expresión regular como parámetro, ¡no me permite especificar un segundo argumento!

Editar:
Esto resultó ser más difícil de lo que pensaba originalmente, así que escribí una pequeña función de prueba para probar todas las soluciones proporcionadas ... se supone que regexIndexOf y regexLastIndexOf se han agregado al objeto 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)]) ;
    }
}

y estoy probando lo siguiente para asegurarme de que al menos para una expresión regular de caracteres, el resultado es el mismo que si usáramos indexOf

// Busque la
prueba a entre las xes ('xxx');
prueba ('axx');
prueba ('xax');
prueba ('xxa');
prueba ('axa');
prueba ('xaa');
prueba ('aax');
prueba ('aaa');

Palmadita
fuente
|por dentro [ ]coincide con el carácter literal |. Probablemente quisiste decir [abc].
Markus Jarderot
sí, gracias, tienes razón, lo arreglaré, pero la expresión regular en sí misma es irrelevante ...
Pat
Actualicé mi respuesta Pat, gracias por cualquier comentario.
Jason Bunting
Encontré que un enfoque más simple y efectivo es usar string.match (/ [AZ] /). Si no hay mucho, el método devuelve nulo; de lo contrario, obtendrá un objeto, puede hacer coincidir (/ [AZ] /). Index para obtener el índice de la primera letra mayúscula
Syler

Respuestas:

129

Combinando algunos de los enfoques ya mencionados (el indexOf es obviamente bastante simple), creo que estas son las funciones que harán el truco:

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

Obviamente, modificar el objeto String incorporado enviaría señales de alerta para la mayoría de las personas, pero esto puede ser una vez cuando no es tan importante; simplemente se consciente de ello.


ACTUALIZACIÓN: Editado regexLastIndexOf()para que parezca imitar lastIndexOf()ahora. Avíseme si aún falla y en qué circunstancias.


ACTUALIZACIÓN: Pasa todas las pruebas que se encuentran en los comentarios de esta página y en la mía. Por supuesto, eso no significa que sea a prueba de balas. Cualquier comentario apreciado.

Jason Bunting
fuente
Su regexLastIndexOfvoluntad sólo se devuelve el índice del último no se solapan partido.
Markus Jarderot
Lo siento, no soy un ENORME tipo regex. ¿Puedes darme un ejemplo que haga que el mío falle? Aprecio poder aprender más, pero su respuesta no ayuda a alguien tan ignorante como yo. :)
Jason Bunting
Jason, acabo de agregar alguna función para probar en la pregunta. esto está fallando (entre otras pruebas) el siguiente 'axx'.lastIndexOf (' a ', 2)! =' axx'.regexLastIndexOf (/ a /, 2)
Pat
2
Creo que es más eficiente de usar en regex.lastIndex = result.index + 1;lugar de regex.lastIndex = ++nextStop;. Se procederá al próximo partido mucho más rápido con suerte sin perder ningún resultado.
Gedrox
1
Si prefiere extraerlo de npm, estas dos funciones de utilidad ahora están en NPM como: npmjs.com/package/index-of-regex
Capaj
185

Las instancias del Stringconstructor tienen un .search()método que acepta un RegExp y devuelve el índice de la primera coincidencia.

Para comenzar la búsqueda desde una posición particular (falsificando el segundo parámetro de .indexOf()) puede slicedesactivar los primeros icaracteres:

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

Pero esto obtendrá el índice en la cadena más corta (después de que se cortó la primera parte), por lo que querrá agregar la longitud de la parte cortada ( i) al índice devuelto si no fue así -1. Esto le dará el índice en la cadena original:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}
Glenn
fuente
1
de la pregunta: Si bien String.search () toma una expresión regular como parámetro, ¡no me permite especificar un segundo argumento!
Pat
14
str.substr (i) .search (/ re /)
Glenn
66
Gran solución, sin embargo, la salida es un poco diferente. indexOf devolverá un número desde el principio (independientemente del desplazamiento), mientras que esto devolverá la posición del desplazamiento. Entonces, por paridad, querrás algo más como esto:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger
39

Tengo una versión corta para ti. ¡Funciona bien para mí!

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

Y si quieres una versión 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;
}

EDITAR : si desea agregar soporte para 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;
}

Para usarlo, tan simple como esto:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);
pmrotule
fuente
Este es realmente un buen truco. Sería genial si lo expandieras para tomar el startIndexparámetro como de costumbre indeoxOfy lastIndexOfhacerlo.
Robert Koritnik el
@RobertKoritnik: edité mi respuesta para apoyar startIndex(o fromIndex). ¡Espero eso ayude!
pmrotule
lastIndexOfRegextambién debe agregar de nuevo el valor de fromIndexal resultado.
Peter
Su algoritmo se dividirá en el siguiente escenario: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));El resultado será 1 cuando debería ser 7, porque indexOf buscará por primera vez el "romeo", sin importar si está al principio de una palabra o no.
KorelK
13

Utilizar:

str.search(regex)

Vea la documentación aquí.

rmg.n3t
fuente
11
@OZZIE: No, en realidad no. Básicamente es la respuesta de Glenn (con ~ 150 votos a favor), excepto que no tiene explicación alguna, no es compatible con la posición de inicio que no sea 0, y fue publicado ... siete años después.
ccjmne
7

Basado en la respuesta de BaileyP. La principal diferencia es que estos métodos regresan -1si el patrón no puede coincidir.

Editar: Gracias a la respuesta de Jason Bunting, tengo una idea. ¿Por qué no modificar la .lastIndexpropiedad de la expresión regular? Aunque esto solo funcionará para patrones con la bandera global ( /g).

Editar: actualizado para pasar los casos de prueba.

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
fuente
Esto parece ser el más prometedor hasta ahora (después de algunas correcciones de sintaxis) :-) Solo fallan algunas pruebas en las condiciones de borde. Cosas como 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Estoy investigando para ver si puedo solucionar esos casos
Pat
6

Podrías usar substr.

str.substr(i).match(/[abc]/);
Andru Luvisi
fuente
Del conocido libro de JavaScript publicado por O'Reilly: "substr no ha sido estandarizado por ECMAScript y, por lo tanto, está en desuso". Pero me gusta la idea básica detrás de lo que está llegando.
Jason Bunting
1
Eso no es un problema. Si REALMENTE le preocupa, use String.substring () en su lugar, solo tiene que hacer las matemáticas un poco diferente. Además, JavaScript no debe estar 100% en deuda con su idioma principal.
Peter Bailey, el
No es un problema: si ejecuta su código en una implementación que no implementa substr porque quieren adherirse a los estándares ECMAScript, tendrá problemas. Por supuesto, reemplazarlo con una subcadena no es tan difícil de hacer, pero es bueno ser consciente de esto.
Jason Bunting
1
En el momento en que tienes problemas tienes una solución muy muy simple. Creo que los comentarios son razonables, pero el voto negativo fue pedante.
VoronoiPotato
¿Podría editar su respuesta para proporcionar un código de demostración que funcione?
vsync
5

RexExplas instancias ya tienen una propiedad lastIndex (si son globales) y, por lo tanto, lo que estoy haciendo es copiar la expresión regular, modificarla ligeramente para adaptarla a nuestros propósitos, execcolocarla en la cadena y mirar el lastIndex. Esto inevitablemente será más rápido que hacer un bucle en la cadena. (Tienes suficientes ejemplos de cómo poner esto en el prototipo de cuerda, ¿verdad?)

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

También puede crear prototipos de las funciones en el objeto 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 explicación rápida de cómo estoy modificando el RegExp: Porque indexOfsolo tengo que asegurarme de que el indicador global esté configurado. Porque lastIndexOfestoy usando una anticipación negativa para encontrar la última ocurrencia a menos que RegExpya coincida al final de la cadena.

Prestaul
fuente
4

No lo hace de forma nativa, pero ciertamente puede agregar esta funcionalidad

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

No probé completamente estos métodos, pero parecen funcionar hasta ahora.

Peter Bailey
fuente
Actualizado para manejar esos casos
Peter Bailey
¡Cada vez que estoy a punto de aceptar esta respuesta, encuentro un nuevo caso! ¡Estos dan resultados diferentes! alerta ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat
bueno, por supuesto que son - str.lastIndexOf hará una coerción de tipo en el patrón - convirtiéndola en una cadena. La cadena "/ [d] /" ciertamente no se encuentra en la entrada, por lo que el -1 devuelto es realmente preciso.
Peter Bailey, el
Entendido. Después de leer la especificación en String.lastIndexOf (), simplemente no entendí cómo funcionaba ese argumento. Esta nueva versión debería manejarlo.
Peter Bailey, el
Algo sigue sin estar bien, pero se está haciendo tarde ... Trataré de obtener un caso de prueba y tal vez arreglarlo por la mañana. Perdón por los problemas hasta ahora.
Pat
2

Después de que todas las soluciones propuestas fallaran mis pruebas de una forma u otra, (editar: algunas se actualizaron para pasar las pruebas después de que escribí esto) encontré la implementación de Mozilla para Array.indexOf y Array.lastIndexOf

Los usé para implementar mi versión de String.prototype.regexIndexOf y String.prototype.regexLastIndexOf de la siguiente manera:

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

Parecen pasar las funciones de prueba que proporcioné en la pregunta.

Obviamente, solo funcionan si la expresión regular coincide con un carácter, pero eso es suficiente para mi propósito, ya que lo usaré para cosas como ([abc], \ s, \ W, \ D)

Seguiré monitoreando la pregunta en caso de que alguien proporcione una implementación mejor / más rápida / más limpia / más genérica que funcione en cualquier expresión regular.

Palmadita
fuente
Wow, eso es un largo código. Verifique mi respuesta actualizada y envíe sus comentarios. Gracias.
Jason Bunting
Esta implementación tiene como objetivo la compatibilidad absoluta con lastIndexOf en Firefox y el motor SpiderMonkey JavaScript, incluso en varios casos que son posiblemente casos extremos. [...] en aplicaciones del mundo real, es posible que pueda calcular con un código menos complicado si ignora esos casos.
Pat
Forme la página de Mozilla :-) Acabo de tomar el código y cambiar dos líneas dejando todos los casos extremos. Dado que algunas de las otras respuestas se actualizaron para aprobar las pruebas, intentaré compararlas y aceptar la más eficiente. Cuando tengo tiempo para volver a visitar el tema.
Pat
Actualicé mi solución y agradezco cualquier comentario o cosas que hagan que falle. Hice un cambio para solucionar el problema de superposición señalado por MizardX (¡con suerte!)
Jason Bunting
2

Necesitaba una regexIndexOffunción también para una matriz, así que la programé yo mismo. Sin embargo, dudo que esté optimizado, pero supongo que debería funcionar correctamente.

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

En ciertos casos simples, puede simplificar su búsqueda hacia atrás utilizando la división.

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

Esto tiene algunos problemas serios:

  1. no se mostrarán coincidencias superpuestas
  2. El índice devuelto es para el final de la partida en lugar del comienzo (está bien si su expresión regular es una constante)

Pero en el lado positivo, es mucho menos código. Para una expresión regular de longitud constante que no puede superponerse (como /\s\w/para encontrar límites de palabras), esto es lo suficientemente bueno.

amwinter
fuente
0

Para datos con coincidencias dispersas, usar string.search es el más rápido en todos los navegadores. Se vuelve a cortar una cadena cada iteración para:

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

Para datos densos hice esto. Es complejo en comparación con el método de ejecución, pero para datos densos, es 2-10 veces más rápido que cualquier otro método que probé, y aproximadamente 100 veces más rápido que la solución aceptada. Los puntos principales son:

  1. Llama a exec en la expresión regular que se pasa una vez para verificar que haya una coincidencia o salir antes. Hago esto usando (? = En un método similar, pero en IE la comprobación con exec es dramáticamente más rápida.
  2. Construye y almacena en caché una expresión regular modificada en el formato '(r). (?!. ? r) '
  3. Se ejecuta la nueva expresión regular y se devuelven los resultados de ese ejecutivo o del primer ejecutivo;

    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 de métodos

No entiendo el propósito de las pruebas en la parte superior. Las situaciones que requieren una expresión regular son imposibles de comparar con una llamada a indexOf, que creo que es el punto de hacer el método en primer lugar. Para que la prueba pase, tiene más sentido usar 'xxx + (?! x)', que ajustar la forma en que itera la expresión regular.

npjohns
fuente
0

El último índice de Jason Bunting no funciona. El mío no es óptimo, pero funciona.

//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
fuente
¿Puedes proporcionar una prueba que haga que la mía falle? Si descubrió que no funciona, proporcione un caso de prueba, ¿por qué simplemente diga "no funciona" y proporcione una solución no óptima?
Jason Bunting
Hoo boy. Tienes toda la razón. Debería haber proporcionado un ejemplo. Desafortunadamente, pasé de este código hace meses y no tengo idea de cuál fue el caso de falla. : - /
Eli
bueno, así es la vida. :)
Jason Bunting
0

Todavía no hay métodos nativos que realicen la tarea solicitada.

Aquí está el código que estoy usando. Imita el comportamiento de los métodos String.prototype.indexOf y String.prototype.lastIndexOf , pero también aceptan un RegExp como argumento de búsqueda además de una cadena que representa el valor a buscar.

Sí, la respuesta es bastante larga, ya que trata de seguir los estándares actuales lo más cerca posible y, por supuesto, contiene una cantidad razonable de comentarios de JSDOC . Sin embargo, una vez minimizado, el código tiene solo 2.27k y una vez comprimido para la transmisión, solo tiene 1023 bytes.

Los 2 métodos a los que esto se suma String.prototype(usando Object.defineProperty donde esté disponible) son:

  1. searchOf
  2. searchLastOf

Pasa todas las pruebas que publicó el OP y, además, he probado las rutinas bastante a fondo en mi uso diario, y he tratado de asegurarme de que funcionen en múltiples entornos, pero los comentarios / problemas siempre son bienvenidos.

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

Si está buscando una búsqueda lastIndex muy simple con RegExp y no le importa si imita lastIndexOf hasta el último detalle, esto puede llamar su atención.

Simplemente invierto la cadena y resta el primer índice de ocurrencia de la longitud - 1. Resulta que pasa mi prueba, pero creo que podría surgir un problema de rendimiento con cadenas largas.

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

Utilicé lo String.prototype.match(regex)que devuelve una matriz de cadenas de todas las coincidencias encontradas de las dadas regexen la cadena (más información, vea aquí ):

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

Use expresiones regulares estándar:

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

Bueno, como solo buscas igualar la posición de un personaje , la expresión regular es posiblemente exagerada.

Supongo que todo lo que quieres es, en lugar de "encontrar primero a este personaje", solo encontrar el primero de estos personajes.

Por supuesto, esta es la respuesta simple, pero hace lo que su pregunta se propone hacer, aunque sin la parte de expresiones regulares (porque no aclaró por qué específicamente tenía que ser una expresión regular)

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
fuente
Solo un comentario sobre el parcheado de monos, si bien soy consciente de sus problemas, ¿cree que es mejor contaminar el espacio de nombres global? No es que los conflictos de símbolos en AMBOS casos no puedan suceder, y básicamente se refactorizan / reparan de la misma manera si surge un problema.
Peter Bailey, el
Bueno, necesito buscar \ sy, en algunos casos, \ W y esperaba no tener que enumerar todas las posibilidades.
Pat
BaileyP: puede solucionar este problema sin la contaminación del espacio de nombres global, es decir: vea jQuery por ejemplo. usa ese modelo. un objeto para proyecto, tus cosas van dentro de él. Mootools dejó un mal sabor de boca.
Kent Fredric
También hay que señalar que nunca codifico como escribí allí. El ejemplo se simplificó por razones de casos de uso.
Kent Fredric