Come trovare gli indici di tutte le occorrenze di una stringa in un'altra in JavaScript?


105

Sto cercando di trovare le posizioni di tutte le occorrenze di una stringa in un'altra stringa, senza distinzione tra maiuscole e minuscole.

Ad esempio, data la stringa:

Ho imparato a suonare l'ukulele in Libano.

e la stringa di ricerca le, voglio ottenere l'array:

[2, 25, 27, 33]

Entrambe le stringhe saranno variabili, ovvero non posso codificare i loro valori.

Ho pensato che questo fosse un compito facile per le espressioni regolari, ma dopo aver lottato per un po 'per trovarne uno che funzionasse, non ho avuto fortuna.

Ho trovato questo esempio di come eseguire questo utilizzo .indexOf(), ma sicuramente ci deve essere un modo più conciso per farlo?

Risposte:


165
var str = "I learned to play the Ukulele in Lebanon."
var regex = /le/gi, result, indices = [];
while ( (result = regex.exec(str)) ) {
    indices.push(result.index);
}

AGGIORNARE

Non sono riuscito a individuare nella domanda originale che la stringa di ricerca deve essere una variabile. Ho scritto un'altra versione per affrontare questo caso che utilizza indexOf, quindi sei tornato da dove hai iniziato. Come sottolineato da Wrikken nei commenti, per fare questo per il caso generale con espressioni regolari avresti bisogno di sfuggire a caratteri speciali regex, a quel punto penso che la soluzione regex diventi più un mal di testa di quanto valga la pena.

function getIndicesOf(searchStr, str, caseSensitive) {
    var searchStrLen = searchStr.length;
    if (searchStrLen == 0) {
        return [];
    }
    var startIndex = 0, index, indices = [];
    if (!caseSensitive) {
        str = str.toLowerCase();
        searchStr = searchStr.toLowerCase();
    }
    while ((index = str.indexOf(searchStr, startIndex)) > -1) {
        indices.push(index);
        startIndex = index + searchStrLen;
    }
    return indices;
}

var indices = getIndicesOf("le", "I learned to play the Ukulele in Lebanon.");

document.getElementById("output").innerHTML = indices + "";
<div id="output"></div>


2
Come sarebbe leuna stringa variabile qui? Anche quando si utilizza new Regexp(str);il pericolo di caratteri speciali è in agguato, $2.50ad esempio la ricerca. Qualcosa di simile regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));sarebbe più vicino IMHO. Non sono sicuro che js abbia un meccanismo di escape delle espressioni regolari incorporato.
Wrikken

new RegExp(searchStr)sarebbe il modo, e sì, nel caso generale dovresti sfuggire ai caratteri speciali. Non vale davvero la pena farlo a meno che tu non abbia bisogno di quel livello di generalità.
Tim Down

1
Ottima risposta e molto utile. Grazie mille, Tim!
Bungle

1
Se la stringa di ricerca è una stringa vuota si ottiene un ciclo infinito ... farebbe un controllo per questo.
HelpMeStackOverflowMyOnlyHope

2
Supponiamo searchStr=aaae quello str=aaaaaa. Quindi invece di trovare 4 occorrenze il tuo codice ne troverà solo 2 perché stai facendo dei salti searchStr.lengthnel ciclo.
blazs

18

Ecco la versione gratuita di regex:

function indexes(source, find) {
  if (!source) {
    return [];
  }
  // if find is empty string return all indexes.
  if (!find) {
    // or shorter arrow function:
    // return source.split('').map((_,i) => i);
    return source.split('').map(function(_, i) { return i; });
  }
  var result = [];
  for (i = 0; i < source.length; ++i) {
    // If you want to search case insensitive use 
    // if (source.substring(i, i + find.length).toLowerCase() == find) {
    if (source.substring(i, i + find.length) == find) {
      result.push(i);
    }
  }
  return result;
}

indexes("I learned to play the Ukulele in Lebanon.", "le")

EDIT : e se vuoi far corrispondere stringhe come 'aaaa' e 'aa' per trovare [0, 2] usa questa versione:

function indexes(source, find) {
  if (!source) {
    return [];
  }
  if (!find) {
      return source.split('').map(function(_, i) { return i; });
  }
  var result = [];
  var i = 0;
  while(i < source.length) {
    if (source.substring(i, i + find.length) == find) {
      result.push(i);
      i += find.length;
    } else {
      i++;
    }
  }
  return result;
}

7
+1. Ho eseguito alcuni test per il confronto con una soluzione che utilizza Regex. Il metodo più veloce è stato quello che utilizzava Regex: jsperf.com/javascript-find-all
StuR

1
Il metodo più veloce è usare indexOf jsperf.com/find-o-substrings
Ethan Yanjia Li

@ LiEthan importerà solo se quella funzione è collo di bottiglia e forse se la stringa di input è lunga.
jcubic

@jcubic La tua soluzione sembra buona, ma ha solo un piccolo confuso. E se chiamo una funzione come questa var result = indexes('aaaa', 'aa')? Il risultato atteso dovrebbe essere [0, 1, 2]o [0, 2]?
Cao Mạnh Quang

@ CaoMạnhQuang guardando il codice il primo risultato. Se vuoi il secondo devi creare il ciclo while e dentro se metti i+=find.length;e in altroi++
jcubic

15

Puoi farlo di sicuro!

//make a regular expression out of your needle
var needle = 'le'
var re = new RegExp(needle,'gi');
var haystack = 'I learned to play the Ukulele';

var results = new Array();//this is the results you want
while (re.exec(haystack)){
  results.push(re.lastIndex);
}

Modifica: impara a scrivere RegExp

Inoltre, ho capito che questo non è esattamente quello che vuoi, poiché lastIndexci dice che la fine dell'ago non è l'inizio, ma è vicino: potresti spingerere.lastIndex-needle.length inserire la matrice dei risultati ...

Modifica: aggiunta di collegamento

La risposta di @Tim Down utilizza l'oggetto dei risultati da RegExp.exec () e tutte le mie risorse Javascript sorvolano sul suo utilizzo (oltre a darti la stringa corrispondente). Quindi quando usaresult.index , è una sorta di Match Object senza nome. Nella descrizione MDC di exec , in realtà descrivono questo oggetto con dettagli decenti.


Ah! Grazie per aver contribuito, in ogni caso, lo apprezzo!
Bungle

9

Un rivestimento utilizzando String.protype.matchAll(ES2020):

[...sourceStr.matchAll(new RegExp(searchStr, 'gi'))].map(a => a.index)

Usando i tuoi valori:

const sourceStr = 'I learned to play the Ukulele in Lebanon.';
const searchStr = 'le';
const indexes = [...sourceStr.matchAll(new RegExp(searchStr, 'gi'))].map(a => a.index);
console.log(indexes); // [2, 25, 27, 33]

Se sei preoccupato di fare uno spread e uno map()in una riga, l'ho eseguito con un for...ofciclo per un milione di iterazioni (usando le tue stringhe). L'unico liner ha una media di 1420 ms mentre la for...ofmedia di 1150 ms sulla mia macchina. Non è una differenza insignificante, ma l'unica linea funzionerà bene se stai facendo solo una manciata di partite.

Vedi matchAllsu caniuse


3

Se vuoi solo trovare la posizione di tutte le partite, vorrei indicarti un piccolo trucco:

var haystack = 'I learned to play the Ukulele in Lebanon.',
    needle = 'le',
    splitOnFound = haystack.split(needle).map(function (culm)
    {
        return this.pos += culm.length + needle.length
    }, {pos: -needle.length}).slice(0, -1); // {pos: ...} – Object wich is used as this

console.log(splitOnFound);

Potrebbe non essere applicabile se hai una RegExp con lunghezza variabile, ma per alcuni potrebbe essere utile.

Questo fa distinzione tra maiuscole e minuscole. Per la mancanza di distinzione tra maiuscole e minuscole utilizzare la String.toLowerCasefunzione prima.


Penso che la tua risposta sia la migliore, perché l'uso di RegExp è pericoloso.
Bharata

1

Ecco un semplice codice

function getIndexOfSubStr(str, searchToken, preIndex, output){
		 var result = str.match(searchToken);
     if(result){
     output.push(result.index +preIndex);
     str=str.substring(result.index+searchToken.length);
     getIndexOfSubStr(str, searchToken, preIndex, output)
     }
     return output;
  };

var str = "my name is 'xyz' and my school name is 'xyz' and my area name is 'xyz' ";
var  searchToken ="my";
var preIndex = 0;

console.log(getIndexOfSubStr(str, searchToken, preIndex, []));


0

Segui la risposta di @jcubic, la sua soluzione ha causato una piccola confusione per il mio caso
Ad esempio var result = indexes('aaaa', 'aa')tornerà [0, 1, 2]invece di [0, 2]
Quindi ho aggiornato un po 'la sua soluzione come sotto per adattarla al mio caso

function indexes(text, subText, caseSensitive) {
    var _source = text;
    var _find = subText;
    if (caseSensitive != true) {
        _source = _source.toLowerCase();
        _find = _find.toLowerCase();
    }
    var result = [];
    for (var i = 0; i < _source.length;) {
        if (_source.substring(i, i + _find.length) == _find) {
            result.push(i);
            i += _find.length;  // found a subText, skip to next position
        } else {
            i += 1;
        }
    }
    return result;
}

0

Grazie per tutte le risposte. Li ho esaminati tutti e ho trovato una funzione che fornisce al primo un ultimo indice di ogni occorrenza della sottostringa "ago". Lo inserisco qui nel caso possa aiutare qualcuno.

Tieni presente che non è la stessa della richiesta originale solo all'inizio di ogni occorrenza. Si adatta meglio al mio caso d'uso perché non è necessario mantenere la lunghezza dell'ago.

function findRegexIndices(text, needle, caseSensitive){
  var needleLen = needle.length,
    reg = new RegExp(needle, caseSensitive ? 'gi' : 'g'),
    indices = [],
    result;

  while ( (result = reg.exec(text)) ) {
    indices.push([result.index, result.index + needleLen]);
  }
  return indices
}

0

Controlla questa soluzione che sarà in grado di trovare anche la stessa stringa di caratteri, fammi sapere se manca qualcosa o non va bene.

function indexes(source, find) {
    if (!source) {
      return [];
    }
    if (!find) {
        return source.split('').map(function(_, i) { return i; });
    }
    source = source.toLowerCase();
    find = find.toLowerCase();
    var result = [];
    var i = 0;
    while(i < source.length) {
      if (source.substring(i, i + find.length) == find)
        result.push(i++);
      else
        i++
    }
    return result;
  }
  console.log(indexes('aaaaaaaa', 'aaaaaa'))
  console.log(indexes('aeeaaaaadjfhfnaaaaadjddjaa', 'aaaa'))
  console.log(indexes('wordgoodwordgoodgoodbestword', 'wordgood'))
  console.log(indexes('I learned to play the Ukulele in Lebanon.', 'le'))


-1
function countInString(searchFor,searchIn){

 var results=0;
 var a=searchIn.indexOf(searchFor)

 while(a!=-1){
   searchIn=searchIn.slice(a*1+searchFor.length);
   results++;
   a=searchIn.indexOf(searchFor);
 }

return results;

}

Cerca le occorrenze di una stringa all'interno di un'altra stringa piuttosto che espressioni regolari.

-1

il codice seguente farà il lavoro per te:

function indexes(source, find) {
  var result = [];
  for(i=0;i<str.length; ++i) {
    // If you want to search case insensitive use 
    // if (source.substring(i, i + find.length).toLowerCase() == find) {
    if (source.substring(i, i + find.length) == find) {
      result.push(i);
    }
  }
  return result;
}

indexes("hello, how are you", "ar")

-2

Usa String.prototype.match .

Ecco un esempio tratto dai documenti MDN:

var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var regexp = /[A-E]/gi;
var matches_array = str.match(regexp);

console.log(matches_array);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']

Questo è piuttosto semplice.
igaurav

11
La domanda è come trovare gli indici degli eventi, non gli stessi eventi!
Luckylooke

1
nonostante questa risposta non corrisponde alla domanda, ma è quello che stavo cercando :)
AlexNikonov
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.