regex - Hoe vind je de indices van alle voorkomens van een string in een andere in JavaScript?
Ik probeer de posities te vinden van alle voorkomen van een tekenreeks in een andere tekenreeks, ongevoelig voor hoofdletters.
Bijvoorbeeld, gegeven de string:
I learned to play the Ukulele in Lebanon.en de zoekstring le, wil ik de array verkrijgen:
[2, 25, 27, 33]
Beide tekenreeksen zullen variabelen zijn - d.w.z. ik kan hun waarden niet hardcoderen.
Ik dacht dat dit een gemakkelijke taak was voor reguliere expressies, maar na een tijdje te hebben geworsteld om er een te vinden die werkte, heb ik geen geluk gehad.
Ik vond dit voorbeeld van hoe dit te bereiken met .indexOf(), maar er moet toch een beknoptere manier zijn om het te doen?
Antwoord #1
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);
}
BIJWERKEN
Ik heb in de originele vraag niet opgemerkt dat de zoekstring een variabele moet zijn. Ik heb een andere versie geschreven om met dit geval om te gaan die indexOf gebruikt, dus je bent weer terug bij waar je begon. Zoals Wrikken in de opmerkingen opmerkte, zou je dit voor het algemene geval met reguliere expressies speciale regex-karakters moeten ontsnappen, op welk punt ik denk dat de regex-oplossing meer hoofdpijn veroorzaakt dan dat het de moeite waard is.
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>Antwoord #2
Eén regel met String.prototype.matchAll (ES2020):
[...sourceStr.matchAll(new RegExp(searchStr, 'gi'))].map(a => a.index)
Gebruikmakend van je waarden:
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]
Als je je zorgen maakt over het uitvoeren van een spread en een map() in één regel, heb ik het uitgevoerd met een for...of-lus voor een miljoen iteraties (met jouw strings). De éénregelige versie gemiddeld 1420ms terwijl de for...of gemiddeld 1150ms op mijn machine. Dat is een niet onaanzienlijk verschil, maar de éénregelige versie werkt prima als je maar een handvol matches doet.
Zie matchAll op caniuse
Antwoord #3
Je kunt dit zeker doen!
//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);
}
Bewerk: leer RegExp spellen
Ook realiseerde ik me dat dit niet precies is wat je wilt, aangezien lastIndex ons het einde van de naald vertelt en niet het begin, maar het komt dicht in de buurt - je zou re.lastIndex-needle.length in de resultatenarray kunnen duwen...
Bewerking: link toevoegen
@Tim Down’s antwoord gebruikt het results-object van RegExp.exec(), en al mijn Javascript-bronnen besteden er weinig aandacht aan (afgezien van het geven van de overeenkomende string). Dus wanneer hij result.index gebruikt, is dat een soort onbenoemd Match Object. In de MDC-beschrijving van exec beschrijven ze dit object eigenlijk redelijk gedetailleerd.
Antwoord #4
Hier is regex-vrije versie:
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")
BEWERKEN: en als je strings zoals 'aaaa' en 'aa' wilt vergelijken om [0, 2] te vinden, gebruik dan deze versie:
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;
}
