Wie finde ich Indizes aller Vorkommen einer Zeichenfolge in einer anderen in JavaScript?

103

Ich versuche, die Positionen aller Vorkommen einer Zeichenfolge in einer anderen Zeichenfolge zu finden, wobei die Groß- und Kleinschreibung nicht berücksichtigt wird.

Beispiel: Geben Sie die Zeichenfolge an:

Ich habe im Libanon Ukulele spielen gelernt.

und die Suchzeichenfolge lemöchte ich das Array erhalten:

[2, 25, 27, 33]

Beide Zeichenfolgen sind Variablen - dh ich kann ihre Werte nicht fest codieren.

Ich dachte, dass dies eine leichte Aufgabe für reguläre Ausdrücke war, aber nachdem ich eine Weile darum gekämpft hatte, eine zu finden, die funktionieren würde, hatte ich kein Glück.

Ich habe dieses Beispiel gefunden, wie dies erreicht werden kann .indexOf(), aber es muss doch einen präziseren Weg geben, dies zu tun?

Patzer
quelle

Antworten:

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

AKTUALISIEREN

In der ursprünglichen Frage konnte ich nicht erkennen, dass die Suchzeichenfolge eine Variable sein muss. Ich habe eine andere Version geschrieben, um diesen Fall zu behandeln, der verwendet wird indexOf, damit Sie wieder dort sind, wo Sie begonnen haben. Wie Wrikken in den Kommentaren hervorhob, müssten Sie, um dies für den allgemeinen Fall mit regulären Ausdrücken zu tun, spezielle Regex-Zeichen vermeiden. An diesem Punkt wird die Regex-Lösung meiner Meinung nach eher zu Kopfschmerzen als zu einem Wert.

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>

Tim Down
quelle
2
Wie wäre lehier eine variable Zeichenfolge? Auch wenn new Regexp(str);die Gefahr von Sonderzeichen lauert, lauert $2.50zum Beispiel. So etwas regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));wäre meiner Meinung nach näher. Ich bin nicht sicher, ob js einen eingebauten Regex-Fluchtmechanismus hat.
Wrikken
new RegExp(searchStr)wäre der Weg, und ja, im allgemeinen Fall müssten Sie Sonderzeichen entkommen. Es lohnt sich nicht wirklich, es sei denn, Sie benötigen dieses Maß an Allgemeinheit.
Tim Down
1
Tolle Antwort und sehr hilfreich. Vielen Dank, Tim!
Bungle
1
Wenn die Suchzeichenfolge eine leere Zeichenfolge ist, erhalten Sie eine Endlosschleife ... würde dies überprüfen.
HelpMeStackOverflowMyOnlyHope
2
Angenommen, searchStr=aaaund das str=aaaaaa. Anstatt 4 Vorkommen zu finden, findet Ihr Code nur 2, da Sie searchStr.lengthin der Schleife überspringen .
Blazs
18

Hier ist die kostenlose Version von 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")

BEARBEITEN : und wenn Sie Zeichenfolgen wie 'aaaa' und 'aa' abgleichen möchten, um [0, 2] zu finden, verwenden Sie diese Version:

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;
}
jcubic
quelle
7
+1. Ich habe einige Tests zum Vergleich mit einer Lösung mit Regex durchgeführt. Die schnellste Methode war die mit Regex: jsperf.com/javascript-find-all
StuR
1
Die schnellste Methode ist die Verwendung von indexOf jsperf.com/find-o-substrings
Ethan Yanjia Li
@LiEthan spielt es nur eine Rolle, ob diese Funktion ein Engpass ist und ob die Eingabezeichenfolge lang ist.
jcubic
@jcubic Deine Lösung scheint gut zu sein, hat aber nur eine kleine Verwirrung. Was ist, wenn ich eine solche Funktion aufrufe var result = indexes('aaaa', 'aa')? Erwartetes Ergebnis sollte sein [0, 1, 2]oder [0, 2]?
Cao Mạnh Quang
@ CaoMạnhQuang beim Betrachten des Codes das erste Ergebnis. Wenn Sie die zweite wollen, müssen Sie while-Schleife und innen erstellen, wenn Sie setzen i+=find.length;und in sonsti++
jcubic
15

Das können Sie sicher!

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

Bearbeiten: Lerne RegExp zu buchstabieren

Außerdem wurde mir klar, dass dies nicht genau das ist , was Sie wollen, da lastIndexdas Ende der Nadel nicht der Anfang ist, aber es ist nah - Sie könnten re.lastIndex-needle.lengthin das Ergebnisarray eindringen ...

Bearbeiten: Link hinzufügen

Die Antwort von @Tim Down verwendet das Ergebnisobjekt von RegExp.exec (), und alle meine Javascript-Ressourcen beschönigen dessen Verwendung (abgesehen davon, dass Sie die übereinstimmende Zeichenfolge erhalten). Wenn er es benutzt result.index, ist das eine Art unbenanntes Match-Objekt. In der MDC-Beschreibung von exec beschreiben sie dieses Objekt tatsächlich sehr detailliert.

Ryley
quelle
Ha! Auf jeden Fall vielen Dank für Ihren Beitrag - ich weiß das zu schätzen!
Bungle
8

Ein Liner mit String.protype.matchAll(ES2020):

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

Verwenden Sie Ihre Werte:

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]

Wenn Sie sich Sorgen machen, einen Spread und einen map()in einer Zeile zu machen, habe ich ihn mit einer for...ofSchleife für eine Million Iterationen (unter Verwendung Ihrer Zeichenfolgen) ausgeführt. Der Einzeiler for...ofbeträgt durchschnittlich 1420 ms, während der Durchschnitt auf meiner Maschine 1150 ms beträgt. Das ist kein unbedeutender Unterschied, aber der eine Liner funktioniert gut, wenn Sie nur eine Handvoll Matches machen.

Siehe matchAllauf caniuse

Benny Hinrichs
quelle
3

Wenn Sie nur die Position aller Übereinstimmungen finden möchten, möchte ich Sie auf einen kleinen Hack hinweisen:

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

Es ist möglicherweise nicht anwendbar, wenn Sie ein RegExp mit variabler Länge haben, aber für einige kann es hilfreich sein.

Dies unterscheidet zwischen Groß- und Kleinschreibung. Bei Unempfindlichkeit gegen Groß- und Kleinschreibung verwenden Sie String.toLowerCasevorher die Funktion.

Hoffmann
quelle
Ich denke, Ihre Antwort ist die beste, weil die Verwendung von RegExp gefährlich ist.
Bharata
1

Hier ist ein einfacher Code

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, []));

Kapil Tiwari
quelle
0

Folgen Sie der Antwort von @jcubic, seine Lösung hat eine kleine Verwirrung für meinen Fall verursacht.
Zum Beispiel wird var result = indexes('aaaa', 'aa')sie [0, 1, 2]anstelle von zurückgegeben. [0, 2]
Also habe ich seine Lösung wie folgt ein wenig aktualisiert, um sie meinem Fall anzupassen

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;
}
Cao Mạnh Quang
quelle
0

Vielen Dank für alle Antworten. Ich habe sie alle durchgesehen und eine Funktion entwickelt, die dem ersten einen letzten Index jedes Auftretens des "Nadel" -Substrings gibt. Ich poste es hier, falls es jemandem hilft.

Bitte beachten Sie, dass dies nicht mit der ursprünglichen Anfrage nur für den Beginn jedes Auftretens identisch ist. Es passt besser zu meinem Anwendungsfall, weil Sie die Nadellänge nicht einhalten müssen.

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
}
Roei Bahumi
quelle
0

Überprüfen Sie diese Lösung, die auch dieselbe Zeichenfolge finden kann. Lassen Sie mich wissen, ob etwas fehlt oder nicht.

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

Jignesh Sanghani
quelle
-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;

}
gaby de wilde
quelle
Dies sucht nach Vorkommen einer Zeichenfolge in einer anderen Zeichenfolge und nicht nach regulären Ausdrücken.
-1

Der folgende Code erledigt die Aufgabe für Sie:

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")
G.Nader
quelle
-2

Verwenden Sie String.prototype.match .

Hier ist ein Beispiel aus den MDN-Dokumenten selbst:

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']
Tejasbubane
quelle
Das ist ziemlich einfach.
Igaurav
11
Die Frage ist, wie man Indizes von Vorkommen findet, nicht von Vorkommen selbst!
Luckylooke
1
Trotz dieser Antwort stimmt die Frage nicht überein, aber das habe ich gesucht :)
AlexNikonov