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

243

En javascript, ¿existe un equivalente de String.indexOf () que toma una expresión regular en lugar de una cadena para el primer primer parámetro y, al mismo tiempo, 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 pensé originalmente, así que escribí una pequeña función de prueba para probar todas las soluciones proporcionadas ... asume 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 de la siguiente manera para asegurarme de que al menos para un carácter regexp, el resultado es el mismo que si usáramos indexOf

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

4
  • |inside [ ]coincide con el carácter literal |. Probablemente quisiste decir [abc]. 7 de nov. De 2008 a las 22:51
  • sí, gracias, tienes razón, lo arreglaré, pero la expresión regular en sí es irrelevante ...
    Pat
    7 de nov. De 2008 a las 23:37
  • Actualicé mi respuesta Pat, gracias por cualquier comentario. 8 de nov. De 2008 a las 19:44
  • 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, obtienes un objeto, puedes hacer coincidir (/ [AZ] /). Index para obtener el índice de la primera letra mayúscula
    Syler
    26/03/19 a las 19:14
204

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

Para iniciar la búsqueda desde una posición en particular (simulando 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 lo fue -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;
}
3
  • 1
    de la pregunta: Si bien String.search () toma una expresión regular como parámetro, ¡no me permite especificar un segundo argumento!
    Pat
    7 de nov. De 2008 a las 22:17
  • 14
    str.substr (i) .search (/ re /)
    Glenn
    8 de nov. De 2008 a las 3:01
  • 7
    Gran solución, sin embargo, el resultado 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, para la paridad, querrá algo más como esto:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; } 10/0814 a las 22:11
147

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

function regexIndexOf(string, regex, startpos) {
    var indexOf = string.substring(startpos || 0).search(regex);
    return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

function regexLastIndexOf(string, regex, startpos) {
    regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : ""));
    if(typeof (startpos) == "undefined") {
        startpos = string.length;
    } else if(startpos < 0) {
        startpos = 0;
    }
    var stringToWorkWith = string.substring(0, startpos + 1);
    var lastIndexOf = -1;
    var nextStop = 0;
    while((result = regex.exec(stringToWorkWith)) != null) {
        lastIndexOf = result.index;
        regex.lastIndex = ++nextStop;
    }
    return lastIndexOf;
}

ACTUALIZACIÓN: Editado regexLastIndexOf()para que parezca imitar lastIndexOf()ahora. Por favor, avíseme si aún falla y bajo qué circunstancias.


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

12
  • Su regexLastIndexOfvoluntad sólo se devuelve el índice del último no se solapan partido. 8 de noviembre de 2008 a las 3:32
  • Lo siento, no soy un tipo ENORME de expresiones regulares, ¿puedes darme un ejemplo que haría que el mío fallara? Aprecio poder aprender más, pero tu respuesta no ayuda a alguien tan ignorante como yo. :) 8 de noviembre de 2008 a las 4:40
  • 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
    8 de nov. De 2008 a las 12:02
  • 2
    Creo que es más eficiente usar en regex.lastIndex = result.index + 1;lugar de regex.lastIndex = ++nextStop;. Pasará al siguiente partido mucho más rápido, con suerte, sin perder ningún resultado.
    Gedrox
    30 de mayo de 2012 a las 9:32
  • 2
    Si prefiere extraerlo de npm, estas dos funciones útiles ahora están en NPM como: npmjs.com/package/index-of-regex
    Capaj
    26/10/2016 a las 19:40
45

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);
5
  • En realidad, este es un buen truco. Sería genial si lo ampliara para tomar también el startIndexparámetro como de costumbre indeoxOfy lastIndexOfhacerlo. 3/04/2015 a las 14:35
  • @RobertKoritnik: edité mi respuesta para apoyar startIndex(o fromIndex). ¡Espero eso ayude! 6/04/2015 a las 7:19
  • lastIndexOfRegextambién debe volver a sumar el valor de fromIndexal resultado.
    Peter
    20/03/18 a las 15:49
  • 2
    Su algoritmo se romperá 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 que aparezca "romeo", sin importar si está al principio de una palabra o no.
    CoralK
    2 de mayo de 2020 a las 12:49
  • Excelente truco. Para manejar la situación que señaló CoralK, puede reemplazar la declaración de retorno de indexOfRegex por: if(match){let list=this.split(regex);match.pop();list.pop();return match.join('').length+list.join('').length+(fromIndex||0);}else return -1;
    yorg
    3 de julio a las 22:17
16

Usar:

str.search(regex)

Consulte la documentación aquí.

1
  • 15
    @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 admite la posición inicial más que 0, y se publicó ... siete años después.
    ccjmne
    3 jun 18 a las 17:00
7

Podrías usar substr.

str.substr(i).match(/[abc]/);
5
  • Del conocido libro de JavaScript publicado por O'Reilly: "substr no ha sido estandarizado por ECMAScript y, por lo tanto, está obsoleto". Pero me gusta la idea básica detrás de lo que quiere decir. 7 de nov. De 2008 a las 23:50
  • 1
    Eso no es un problema. Si está REALMENTE preocupado, use String.substring () en su lugar, solo tiene que hacer las matemáticas de manera un poco diferente. Además, JavaScript no debe estar 100% en deuda con su idioma principal. 7 de nov. De 2008 a las 23:59
  • No es un problema: si hace que su código se ejecute en una implementación que no implementa substr porque quiere adherirse a los estándares ECMAScript, tendrá problemas. Por supuesto, reemplazarlo con subcadena no es tan difícil de hacer, pero es bueno ser consciente de esto. 8 de noviembre de 2008 a las 0:36
  • 1
    En el momento en que tienes problemas, tienes soluciones muy sencillas. Creo que los comentarios son razonables, pero el voto negativo fue pedante. 28 de marzo de 2013 a las 13:15
  • ¿Podría editar su respuesta para proporcionar un código de demostración que funcione?
    vsync
    11/06/18 a las 22:08
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, tuve 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;
}
1
  • Esto parece ser el más prometedor hasta ahora (después de algunas correcciones de sintaxis) :-) Solo fallando algunas pruebas en las condiciones de borde. Cosas como 'axx'.lastIndexOf (' a ', 0)! =' Axx'.regexLastIndexOf (/ a /, 0) ... Lo estoy investigando para ver si puedo solucionar esos casos
    Pat
    8 de nov. De 2008 a las 12:09
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 que se adapte a nuestros propósitos, execcolocarla en la cadena y mirar la extensiónlastIndex . Esto inevitablemente será más rápido que hacer un bucle en la cuerda. (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 un prototipo 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 RegExp: Porque indexOfsolo tengo que asegurarme de que la bandera global esté configurada. Por lastIndexOfejemplo, estoy usando un avance negativo para encontrar la última aparición a menos que RegExpya coincida al final de la cadena.

1
  • Error tipográfico "RexExp" al principio
    Nirvana
    1 de mayo a las 7:03
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.

7
  • Actualizado para manejar esos casos 7 de nov. De 2008 a las 22:57
  • ¡Cada vez que estoy a punto de aceptar esta respuesta, encuentro un nuevo caso! ¡Estos dan resultados diferentes! alert ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
    Pat
    7 de nov. De 2008 a las 23:20
  • bueno, por supuesto que lo son: str.lastIndexOf hará la coerción de tipo en el patrón, convirtiéndolo en una cadena. La cadena "/ [d] /" ciertamente no se encuentra en la entrada, por lo que el -1 devuelto es realmente exacto. 7 de nov. De 2008 a las 23:29
  • Entiendo. Después de leer la especificación en String.lastIndexOf (), entendí mal cómo funcionaba ese argumento. Esta nueva versión debería manejarlo. 7 de nov. De 2008 a las 23:39
  • Algo todavía no está bien, pero se está haciendo tarde ... Intentaré obtener un caso de prueba y tal vez arreglarlo por la mañana. Perdón por las molestias hasta ahora.
    Pat
    7 de nov. De 2008 a las 23:55
2

Después de que todas las soluciones propuestas fallaron en 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;
  };

Parece que pasan 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 la 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.

4
  • Vaya, eso es un código largo. Por favor revise mi respuesta actualizada y envíe sus comentarios. Gracias. 8 de nov. De 2008 a las 19:38
  • Esta implementación tiene como objetivo la compatibilidad absoluta con lastIndexOf en Firefox y el motor de JavaScript SpiderMonkey, incluidos en varios casos que posiblemente sean casos extremos. [...] en aplicaciones del mundo real, es posible que pueda calcular con un código menos complicado si ignora esos casos.
    Pat
    8 de nov. De 2008 a las 22:26
  • Forme la página de mozilla :-) Acabo de tomar el anuncio de código y cambiar dos líneas dejando todos los casos extremos. Dado que un par de las otras respuestas se actualizaron para pasar las pruebas, intentaré compararlas y aceptaré la más eficiente. Cuando tenga tiempo de revisar el tema.
    Pat
    8 de nov. De 2008 a las 22:48
  • Actualicé mi solución y agradezco cualquier comentario o cosa que cause que falle. Hice un cambio para solucionar el problema de superposición señalado por MizardX (¡con suerte!) 8 de noviembre de 2008 a las 23:20
2

Necesitaba una regexIndexOffunción también para una matriz, así que programé una 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);
1

En ciertos casos simples, puede simplificar su búsqueda hacia atrás usando split.

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

  1. las coincidencias superpuestas no aparecerán
  2. el índice devuelto es para el final de la coincidencia 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 se puede superponer (como /\s\w/para encontrar límites de palabras), esto es lo suficientemente bueno.

0

Para datos con coincidencias escasas, el uso de string.search es el más rápido en todos los navegadores. Vuelve a cortar una cadena en 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 de 2 a 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 al ejecutivo en la expresión regular pasada una vez para verificar que hay una coincidencia o salir temprano. Hago esto usando (? = En un método similar, pero en IE, verificar con exec es dramáticamente más rápido.
  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 exec o del primer exec;

    function lastIndexOfGroupSimple(string, regex, index) {
        if (index === 0 || index) string = string.slice(0, Math.max(0, index + 1));
        regex.lastIndex = 0;
        var lastRegex, index
        flags = 'g' + (regex.multiline ? 'm' : '') + (regex.ignoreCase ? 'i' : ''),
        key = regex.source + '$' + flags,
        match = regex.exec(string);
        if (!match) return -1;
        if (lastIndexOfGroupSimple.cache === undefined) lastIndexOfGroupSimple.cache = {};
        lastRegex = lastIndexOfGroupSimple.cache[key];
        if (!lastRegex)
            lastIndexOfGroupSimple.cache[key] = lastRegex = new RegExp('.*(' + regex.source + ')(?!.*?' + regex.source + ')', flags);
        index = match.index;
        lastRegex.lastIndex = match.index;
        return (match = lastRegex.exec(string)) ? lastRegex.lastIndex - match[1].length : index;
    };
    

jsPerf de métodos

No entiendo el propósito de las pruebas arriba. 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.

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;
}
3
  • ¿Puede proporcionar una prueba que haga que la mía falle? Si descubrió que no funciona, proporcione un caso de prueba, ¿por qué simplemente decir "no funciona" y proporcionar una solución no óptima en su lugar? 10/11/15 a las 20:37
  • 1
    Hoo chico. Tienes toda la razón. Debería haber dado un ejemplo. Desafortunadamente, pasé de este código hace meses y no tengo idea de cuál fue el caso de falla. : - /
    Eli
    11/11/15 a las 22:00
  • bueno, así es la vida. :) 17/11/15 a las 21:45
0

Todavía no existen 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 una 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 intenta seguir los estándares actuales lo más cerca posible y, por supuesto, contiene una cantidad razonable de comentarios JSDOC . Sin embargo, una vez minimizado, el código tiene solo 2.27k y una vez comprimido con gzip para su transmisión, solo tiene 1023 bytes.

Los 2 métodos que esto agrega 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 intentado asegurarme de que funcionan en varios 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>
0

Si está buscando una búsqueda de 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 resto el primer índice de ocurrencia de la longitud - 1. Sucede 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;
};
0

Usé String.prototype.match(regex)que devuelve una matriz de cadenas de todas las coincidencias encontradas de lo dado regexen la cadena (más información, ver 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));
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
}

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

Para obtener una solución que sea más concisa que la mayoría de las otras respuestas publicadas, es posible que desee utilizar la String.prototype.replacefunción que ejecutará una función en cada patrón detectado. Por ejemplo:

let firstIndex = -1;
"the 1st numb3r".replace(/\d/,(p,i) => { firstIndex = i; });
// firstIndex === 4

Esto es especialmente útil para el caso del "último índice":

let lastIndex = -1;
"the l4st numb3r".replace(/\d/g,(p,i) => { lastIndex = i; });
// lastIndex === 13

Aquí, es importante incluir el modificador "g" para que se evalúen todas las ocurrencias. Estas versiones también darán como resultado -1si no se encontró la expresión regular.

Finalmente, aquí están las funciones más generales que incluyen un índice de inicio:

function indexOfRegex(str,regex,start = 0) {
    regex = regex.global ? regex : new RegExp(regex.source,regex.flags + "g");
    let index = -1;
    str.replace(regex,function() {
        const pos = arguments[arguments.length - 2];
        if(index < 0 && pos >= start)
            index = pos;
    });
    return index;
}

function lastIndexOfRegex(str,regex,start = str.length - 1) {
    regex = regex.global ? regex : new RegExp(regex.source,regex.flags + "g");
    let index = -1;
    str.replace(regex,function() {
        const pos = arguments[arguments.length - 2];
        if(pos <= start)
            index = pos;
    });
    return index;
}

Estas funciones evitan específicamente dividir la cadena en el índice de inicio, lo que creo que es riesgoso en la era de Unicode. No modifican el prototipo de clases comunes de Javascript (aunque puede hacerlo usted mismo). Aceptan más indicadores RegExp, por ejemplo, "u" o "s" y cualquier indicador que se pueda agregar en el futuro. Y me resulta más fácil razonar sobre las funciones de devolución de llamada que los bucles for / while.

0

let regExp; // your RegExp here
arr.map(x => !!x.toString().match(regExp)).indexOf(true)
Nuevo colaborador
user16864806 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
1
  • Agregue más detalles para ampliar su respuesta, como código de trabajo o citas de documentación. 9 de septiembre a las 3:52
-2

Bueno, como solo busca coincidir con 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 de estos este personaje", simplemente encuentra el primero de estos personajes.

Esta, por supuesto, es la respuesta simple, pero hace lo que su pregunta se propone hacer, aunque sin la parte de expresión regular (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
4
  • Solo un comentario sobre el parche de monos, aunque 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 en caso de que surja un problema. 7 de nov. De 2008 a las 23:56
  • Bueno, necesito buscar \ sy en algunos casos \ W y esperaba no tener que enumerar todas las posibilidades.
    Pat
    8 de nov. De 2008 a las 0:00
  • BaileyP: puede solucionar este problema sin contaminación global del espacio de nombres, es decir, consulte jQuery, por ejemplo. usa ese modelo. un objeto por proyecto, sus cosas van dentro de él. Mootools me dejó un mal sabor de boca. 8 de noviembre de 2008 a las 6:33
  • También debe tenerse en cuenta que nunca codifiqué como escribí allí. el ejemplo se simplificó por razones de casos de uso. 8 de noviembre de 2008 a las 6:36