Existe uma versão do String.indexOf () do JavaScript que permite expressões regulares?

214

Em javascript, existe um equivalente a String.indexOf () que usa uma expressão regular em vez de uma string para o primeiro primeiro parâmetro enquanto ainda permite um segundo parâmetro?

Eu preciso fazer algo como

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

e

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

Enquanto String.search () usa uma regexp como parâmetro, ela não me permite especificar um segundo argumento!

Edit:
Isso acabou sendo mais difícil do que eu pensava, então escrevi uma pequena função de teste para testar todas as soluções fornecidas ... pressupõe que regexIndexOf e regexLastIndexOf foram adicionados ao 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)]) ;
    }
}

e estou testando a seguir para garantir que pelo menos para uma expressão regular de caracteres, o resultado seja o mesmo que se usássemos indexOf

// Procure o a entre o
teste xes ('xxx');
teste ('axx');
teste ('xax');
teste ('xxa');
teste ('axa');
teste ('xaa');
teste ('aax');
teste ('aaa');

Pat
fonte
|inside [ ]corresponde ao caractere literal |. Você provavelmente quis dizer [abc].
Markus Jarderot
sim graças você está certo, eu vou corrigi-lo, mas a própria regexp é irrelevante ...
Pat
Atualizei minha resposta Pat, obrigado por qualquer feedback.
23880 Jason Bunting
Eu descobri que uma abordagem mais simples e eficaz é usar apenas string.match (/ [AZ] /). Se não houver muito, o método retornará nulo; caso contrário, você obterá um objeto, poderá corresponder (/ [AZ] /). Index para obter o índice da primeira letra maiúscula
Syler

Respostas:

129

Combinando algumas das abordagens já mencionadas (o indexOf é obviamente bastante simples), acho que estas são as funções que farão o truque:

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 o objeto String incorporado enviaria sinalizadores vermelhos para a maioria das pessoas, mas pode ser uma vez em que não é grande coisa; simplesmente esteja ciente disso.


ATUALIZAÇÃO: Editado de regexLastIndexOf()modo que parece imitar lastIndexOf()agora. Informe-me se ainda falhar e em que circunstâncias.


ATUALIZAÇÃO: passa em todos os testes encontrados nos comentários desta página e nos meus. Claro, isso não significa que é à prova de balas. Qualquer feedback apreciado.

Jason Bunting
fonte
Você regexLastIndexOfretornará apenas o índice da última correspondência sem sobreposição .
Markus Jarderot
Desculpe, não um cara regex ENORME - você pode me dar um exemplo que faria o meu falhar? Aprecio poder aprender mais, mas sua resposta não ajuda alguém tão ignorante quanto eu. :)
Jason Bunting 8/08
Jason Acabei de adicionar alguma função para testar na pergunta. este está a falhar (entre os outros testes) a seguinte 'axx'.lastIndexOf (' um '2) =!' axx'.regexLastIndexOf (/ a /, 2)
Pat
2
Eu acho que é mais eficiente usar em regex.lastIndex = result.index + 1;vez de regex.lastIndex = ++nextStop;. Espera-se que vá para a próxima partida muito mais rápido, sem perder nenhum resultado.
Gedrox
1
Se preferir tirá-lo do NPM, estes dois util funções estão agora em NPM como: npmjs.com/package/index-of-regex
Capaj
185

Instâncias do Stringconstrutor têm um .search()método que aceita um RegExp e retorna o índice da primeira correspondência.

Para iniciar a pesquisa a partir de uma posição específica (fingindo o segundo parâmetro de .indexOf()), você pode slicedesativar os primeiros icaracteres:

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

Mas isso fará com que o índice fique na string mais curta (após a primeira parte ter sido cortada), então você desejará adicionar o comprimento da parte cortada ( i) ao índice retornado, se não estiver -1. Isso fornecerá o índice na string original:

function regexIndexOf(text, re, i) {
    var indexInSuffix = text.slice(i).search(re);
    return indexInSuffix < 0 ? indexInSuffix : indexInSuffix + i;
}
Glenn
fonte
1
da pergunta: Enquanto String.search () usa um regexp como parâmetro, ele não me permite especificar um segundo argumento!
Pat
14
str.substr (i) .search (/ re /)
Glenn
6
Ótima solução, no entanto, a saída é um pouco diferente. indexOf retornará um número desde o início (independentemente do deslocamento), enquanto isso retornará a posição do deslocamento. Assim, para a paridade, você vai querer algo mais parecido com isto:function regexIndexOf(text, offset) { var initial = text.substr(offset).search(/re/); if(initial >= 0) { initial += offset; } return initial; }
gkoberger
39

Eu tenho uma versão curta para você. Funciona bem para mim!

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

E se você quiser uma versão protótipo:

String.prototype.indexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.indexOf(match[0]) : -1;
}

String.prototype.lastIndexOfRegex = function(regex){
  var match = this.match(regex);
  return match ? this.lastIndexOf(match[match.length-1]) : -1;
}

EDIT : se você deseja adicionar suporte 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 usá-lo, simples assim:

var firstIndex = str.indexOfRegex(/[abc]/gi);
var lastIndex  = str.lastIndexOfRegex(/[abc]/gi);
pmrotule
fonte
Este é realmente um bom truque. Seria ótimo se você o expandisse para também pegar o startIndexparâmetro como de costume indeoxOfe lastIndexOffazer.
Robert Koritnik
@RobertKoritnik - editei minha resposta para apoiar startIndex(ou fromIndex). Espero que ajude!
Pmrotule
lastIndexOfRegextambém deve adicionar novamente o valor de fromIndexao resultado.
Peter
Seu algoritmo será interrompido no seguinte cenário: "aRomeo Romeo".indexOfRegex(new RegExp("\\bromeo", 'gi'));O resultado será 1 quando deve ser 7, porque o indexOf procurará pela primeira vez o "romeo" aparecer, não importa se está no início de uma palavra ou não.
KorelK
13

Usar:

str.search(regex)

Veja a documentação aqui.

rmg.n3t
fonte
11
@ZZIE: Não, na verdade não. É basicamente a resposta de Glenn (com ~ 150 upvotes), exceto que não tem explicação alguma, não suporta posição inicial além de 0, e foi publicada ... sete anos depois.
Ccjmne 3/06
7

Baseado na resposta de BaileyP. A principal diferença é que esses métodos retornam -1se o padrão não puder ser correspondido.

Edit: Graças à resposta de Jason Bunting, tive uma idéia. Por que não modificar a .lastIndexpropriedade do regex? Embora isso funcione apenas para padrões com a bandeira global ( /g).

Edit: Atualizado para passar nos casos de teste.

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
fonte
Até agora, parece o mais promissor (após algumas correções de sintaxe) :-) Apenas falhou em alguns testes nas condições de borda. Coisas como 'axx'.lastIndexOf (' a '0) =!' Axx'.regexLastIndexOf (/ a /, 0) ... Eu estou olhando para ela, para ver se eu posso corrigir esses casos
Pat
6

Você poderia usar substr.

str.substr(i).match(/[abc]/);
Andru Luvisi
fonte
Do conhecido livro JavaScript publicado por O'Reilly: "o substr não foi padronizado pelo ECMAScript e, portanto, está obsoleto". Mas eu gosto da idéia básica por trás do que você está recebendo.
Jason Bunting
1
Isso não é problema. Se você está realmente preocupado com isso, use String.substring () - basta fazer as contas de maneira um pouco diferente. Além disso, o JavaScript não deve ser 100% garantido pela sua língua-mãe.
Peter Bailey
Não é um problema: se você executar seu código em uma implementação que não implementa substr porque eles desejam aderir aos padrões do ECMAScript, você terá problemas. É verdade que substituí-lo por substring não é tão difícil de fazer, mas é bom estar ciente disso.
237 Jason Bunting
1
No momento em que você tem problemas, você tem soluções muito, muito simples. Eu acho que os comentários são sensatos, mas o voto negativo foi pedante.
VoronoiPotato
Você poderia editar sua resposta para fornecer um código de demonstração funcional?
vsync
5

RexExpAs instâncias já têm uma propriedade lastIndex (se forem globais) e, portanto, o que estou fazendo é copiar a expressão regular, modificá-la levemente para se adequar aos nossos propósitos, execinseri-la na string e examinar o lastIndex. Isso inevitavelmente será mais rápido do que dar laços na corda. (Você tem exemplos suficientes de como colocar isso no protótipo de string, certo?)

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

Você também pode criar um protótipo das funções no 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

Uma rápida explicação de como eu estou modificando o RegExp: Pois indexOfeu apenas tenho que garantir que a bandeira global esteja definida. Para lastIndexOf, estou usando uma observação negativa para encontrar a última ocorrência, a menos que RegExpjá esteja correspondendo no final da string.

Prestaul
fonte
4

Não é nativo, mas você certamente pode adicionar essa funcionalidade

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

Não testei completamente esses métodos, mas eles parecem funcionar até agora.

Peter Bailey
fonte
Atualizado para lidar com esses casos
Peter Bailey
Sempre que estou prestes a aceitar esta resposta, encontro um novo caso! Estes dão resultados diferentes! alerta ([str.lastIndexOf (/ [d] /, 4), str.regexLastIndexOf (/ [d] /, 4)]);
Pat
bem, é claro que eles são - str.lastIndexOf digitará coerção no padrão - convertendo-o em uma string. A string "/ [d] /" certamente não é encontrada na entrada, portanto o -1 retornado é realmente preciso.
Peter Bailey
Entendi. Depois de ler as especificações em String.lastIndexOf () - apenas entendi mal como esse argumento funcionava. Esta nova versão deve lidar com isso.
Peter Bailey
Algo ainda não está certo, mas está ficando tarde ... vou tentar obter um caso de teste e talvez consertá-lo pela manhã. Desculpe pelo problema até agora.
Pat
2

Depois de todas as soluções propostas falharem nos meus testes, de uma maneira ou de outra (editar: algumas foram atualizadas para passar nos testes depois que escrevi isso), encontrei a implementação do mozilla para Array.indexOf e Array.lastIndexOf

Eu os usei para implementar minha versão de String.prototype.regexIndexOf e String.prototype.regexLastIndexOf da seguinte maneira:

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

Eles parecem passar nas funções de teste que forneci na pergunta.

Obviamente, eles só funcionam se a expressão regular corresponder a um caractere, mas isso é suficiente para o meu objetivo, pois eu a usarei para coisas como ([abc], \ s, \ W, \ D)

Continuarei monitorando a pergunta caso alguém forneça uma implementação melhor / mais rápida / mais limpa / mais genérica que funcione em qualquer expressão regular.

Pat
fonte
Uau, isso é um longo pedaço de código. Verifique minha resposta atualizada e forneça feedback. Obrigado.
23880 Jason Bunting
Esta implementação visa a compatibilidade absoluta com lastIndexOf no Firefox e no mecanismo JavaScript SpiderMonkey, incluindo em vários casos que são indiscutivelmente casos extremos. [...] em aplicativos do mundo real, você pode calcular com um código menos complicado se ignorar esses casos.
Pat
Forme a página mozilla :-) Acabei de pegar o código e alterar duas linhas, deixando todos os casos extremos. Como algumas das outras respostas foram atualizadas para passar nos testes, tentarei compará-las e aceitarei as mais eficazes. Quando tiver tempo para revisar o problema.
Pat
Atualizei minha solução e aprecio qualquer comentário ou algo que a faça falhar. Eu fiz uma mudança para corrigir o problema de sobreposição apontado por MizardX (espero!)
Jason Bunting
2

Eu também precisava de uma regexIndexOffunção para uma matriz, então programei uma. No entanto, duvido que seja otimizado, mas acho que deve funcionar corretamente.

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

Em certos casos simples, você pode simplificar sua pesquisa para trás usando divisão.

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

Isso tem alguns problemas sérios:

  1. correspondências sobrepostas não aparecerão
  2. O índice retornado é para o final da partida e não o início (bom se o seu regex for uma constante)

Mas o lado positivo é muito menos código. Para um regex de comprimento constante que não pode se sobrepor (como /\s\w/para encontrar limites de palavras), isso é bom o suficiente.

amwinter
fonte
0

Para dados com correspondências esparsas, o uso de string.search é o mais rápido entre os navegadores. Ele divide novamente uma string a cada iteração 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 dados densos eu fiz isso. É complexo comparado ao método execute, mas para dados densos, é 2-10x mais rápido do que qualquer outro método que eu tentei e cerca de 100x mais rápido que a solução aceita. Os pontos principais são:

  1. Ele chama exec no regex transmitido uma vez para verificar se há uma correspondência ou sair mais cedo. Eu faço isso usando (? = Em um método semelhante, mas no IE, a verificação com o exec é drasticamente mais rápida.
  2. Ele constrói e armazena em cache um regex modificado no formato '(r). (?!. ? r) '
  3. O novo regex é executado e os resultados desse exec ou do primeiro exec são retornados;

    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

Eu não entendo o propósito dos testes lá em cima. É impossível comparar situações que exigem um regex com uma chamada para indexOf, que eu acho que é o ponto de fazer o método em primeiro lugar. Para que o teste seja aprovado, faz mais sentido usar 'xxx + (?! x)' do que ajustar a maneira como o regex itera.

npjohns
fonte
0

O último índice de Jason Bunting não funciona. O meu não é o ideal, mas 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
fonte
Você pode fornecer um teste que faça com que o meu falhe? Se você achou que não funciona, forneça um caso de teste, por que dizer "não funciona" e forneça uma solução não ideal?
Jason Bunting
Olá garoto. Você está totalmente certo. Eu deveria ter dado um exemplo. Infelizmente, mudei de código há meses e não tenho idéia de qual foi o caso de falha. : - /
Eli
bem, assim é a vida. :)
Jason Bunting
0

Ainda não existem métodos nativos que executam a tarefa solicitada.

Aqui está o código que estou usando. Ele imita o comportamento de String.prototype.indexOf e String.prototype.lastIndexOf métodos, mas eles também aceitar um RegExp como o argumento de pesquisa, além de uma cadeia que representa o valor a procurar.

Sim, é bastante longo para responder, pois tenta seguir os padrões atuais o mais próximo possível e, é claro, contém uma quantidade razoável de comentários do JSDOC . No entanto, uma vez minificado, o código tem apenas 2,27k e uma vez compactado para transmissão, tem apenas 1023 bytes.

Os 2 métodos que isso adiciona String.prototype(usando Object.defineProperty, quando disponível) são:

  1. searchOf
  2. searchLastOf

Ele passa em todos os testes que o OP postou e, adicionalmente, eu testei as rotinas completamente no meu uso diário, e tentei garantir que elas funcionassem em vários ambientes, mas comentários / problemas são sempre bem-vindos.

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

Se você estiver procurando uma pesquisa lastIndex muito simples com o RegExp e não se importa se imita lastIndexOf até o último detalhe, isso pode chamar sua atenção.

Simplesmente inverto a string e subtraio o primeiro índice de ocorrência do comprimento - 1. Isso passa no meu teste, mas acho que poderia surgir um problema de desempenho com longas strings.

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

Eu usei o String.prototype.match(regex)que retorna uma matriz de seqüência de caracteres de todas as correspondências encontradas dos dados regexna seqüência de caracteres (mais informações, veja aqui ):

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
fonte
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 expressões regulares padrão:

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

Bem, como você está apenas procurando corresponder à posição de um personagem , regex é possivelmente um exagero.

Presumo que tudo o que você quer é que, em vez de "encontrar primeiro esse personagem", encontre primeiro o primeiro.

É claro que essa é a resposta simples, mas faz o que sua pergunta se propõe a fazer, embora sem a parte de regex (porque você não esclareceu por que, especificamente, tinha que ser uma regex)

function mIndexOf( str , chars, offset )
{
   var first  = -1; 
   for( var i = 0; i < chars.length;  i++ )
   {
      var p = str.indexOf( chars[i] , offset ); 
      if( p < first || first === -1 )
      {
           first = p;
      }
   }
   return first; 
}
String.prototype.mIndexOf = function( chars, offset )
{
   return mIndexOf( this, chars, offset ); # I'm really averse to monkey patching.  
};
mIndexOf( "hello world", ['a','o','w'], 0 );
>> 4 
mIndexOf( "hello world", ['a'], 0 );
>> -1 
mIndexOf( "hello world", ['a','o','w'], 4 );
>> 4
mIndexOf( "hello world", ['a','o','w'], 5 );
>> 6
mIndexOf( "hello world", ['a','o','w'], 7 );
>> -1 
mIndexOf( "hello world", ['a','o','w','d'], 7 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 10 );
>> 10
mIndexOf( "hello world", ['a','o','w','d'], 11 );
>> -1
Kent Fredric
fonte
Apenas um comentário sobre o patch de macacos - enquanto eu estou ciente de seus problemas - você acha que poluir o espaço para nome global é melhor? Não é como se os conflitos de símbolos em AMBOS os casos não pudessem acontecer e fossem basicamente refatorados / reparados da mesma maneira em caso de problemas.
Peter Bailey
Bem, eu preciso procurar \ se, em alguns casos, \ W e esperava não precisar enumerar todas as possibilidades.
Pat
BaileyP: você pode contornar esse problema sem poluição do espaço para nome global, ou seja: veja o jQuery, por exemplo. use esse modelo. um objeto para o projeto, suas coisas vão para dentro dele. Mootools deixou um gosto ruim na minha boca.
21750 Kent
Note também que nunca codifico como escrevi lá. o exemplo foi simplificado por motivos de casos de uso.
21750 Kent Kentric