JavaScript'te bir dizenin diğerindeki tüm oluşumlarının dizinlerini nasıl bulabilirim?


106

Başka bir dizedeki bir dizenin tüm oluşumlarının konumlarını büyük / küçük harfe duyarlı olarak bulmaya çalışıyorum.

Örneğin, dize verildiğinde:

Lübnan'da Ukulele çalmayı öğrendim.

ve arama dizesi le, diziyi elde etmek istiyorum:

[2, 25, 27, 33]

Her iki dizge de değişken olacaktır - yani değerlerini sabit kodlayamıyorum.

Bunun normal ifadeler için kolay bir görev olduğunu düşündüm, ancak işe yarayacak birini bulmak için bir süre uğraştıktan sonra şansım olmadı.

Bunu kullanarak nasıl başaracağıma dair bu örneği buldum .indexOf(), ama kesinlikle bunu yapmanın daha özlü bir yolu olmalı?

Yanıtlar:


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

GÜNCELLEME

Orijinal soruda arama dizesinin bir değişken olması gerektiğini fark edemedim. Bu vakayla ilgilenmek için başka bir versiyon yazdım indexOf, yani başladığınız yere geri döndünüz. Wrikken'in yorumlarda belirttiği gibi, bunu genel durumda normal ifadelerle yapmak için özel normal ifade karakterlerinden kaçmanız gerekir, bu noktada normal ifade çözümünün değerinden çok baş ağrısına dönüştüğünü düşünüyorum.

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
leBurada değişken bir dizge nasıl olur ? new Regexp(str);Özel karakter tehlikesini kullanırken bile , $2.50örneğin aramak pusuda . regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));IMHO gibi bir şey daha yakın olacaktır. Js'nin yerleşik bir normal ifade çıkış mekanizması olup olmadığından emin değilim.
Wrikken

new RegExp(searchStr)yol bu olabilir ve evet, genel durumda özel karakterlerden kaçmanız gerekir. Bu düzeyde bir genelliğe ihtiyacınız olmadıkça gerçekten yapmaya değmez.
Tim Down

1
Harika cevap ve çok faydalı. Çok teşekkürler Tim!
Bungle

1
Arama dizesi boş bir dizge ise sonsuz bir döngü elde edersiniz ... bunun için bir kontrol yapacaktır.
HelpMeStackOverflowMyOnlyHope

3
Varsayalım searchStr=aaave bu str=aaaaaa. O zaman 4 oluşum bulmak yerine kodunuz sadece 2 tane bulacaktır çünkü searchStr.lengthdöngüde atlama yapıyorsunuz .
2017

19

İşte normal ifadelerin ücretsiz sürümü:

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

DÜZENLEME : ve "aaaa" ve "aa" gibi dizeleri eşleştirmek istiyorsanız [0, 2] 'yi bulmak için bu sürümü kullanın:

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. Regex kullanarak bir çözümle karşılaştırma yapmak için bazı testler yaptım. En hızlı yöntem Regex'i
StuR

1
En hızlı yöntem indexOf jsperf.com/find-o-substrings
Ethan Yanjia Li

@LiEthan, yalnızca bu işlevin darboğaz olup olmadığı ve belki de girdi dizesinin uzun olup olmadığı önemli olacaktır.
jcubic

@jcubic Çözümünüz iyi görünüyor, ancak küçük bir kafa karışıklığı var. Ya böyle bir işlevi çağırırsam var result = indexes('aaaa', 'aa')? Beklenen sonuç [0, 1, 2]veya [0, 2]?
Cao Mạnh Quang

@ CaoMạnhQuang koda bakarken ilk sonuca. İkincisini istiyorsanız, döngü içinde i+=find.length;ve başka bir şey koyarsanız içinde oluşturmanız gerekiri++
jcubic

15

Bunu kesinlikle yapabilirsin!

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

Düzen: RegExp'i yazmayı öğrenin

Ayrıca, iğnenin sonunun başlangıcı olmadığını söylediği gibi , bunun tam olarak istediğiniz şey olmadığını fark ettim lastIndex, ama çok yakın - re.lastIndex-needle.lengthsonuç dizisine girebilirsiniz ...

Düzenleme: bağlantı ekleme

@Tim Down'ın cevabı, RegExp.exec () 'den sonuç nesnesini kullanır ve benim tüm Javascript kaynaklarım onun kullanımına göre (size eşleşen dizeyi vermenin dışında) parlar. Yani kullandığı zaman result.index, bu bir çeşit isimsiz Eşleşme Nesnesi. Gelen exec MDC açıklaması , aslında iyi ayrıntılı olarak bu nesneyi tarif eder.


Ha! Katkıda bulunduğunuz için teşekkürler, her halükarda minnettarım!
Bungle

10

String.protype.matchAll(ES2020) kullanan bir astar :

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

Değerlerinizi kullanarak:

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]

Bir yayılma ve bir map()satırda yapmaktan endişeleniyorsanız , for...ofbir milyon yineleme için (dizelerinizi kullanarak) bir döngü ile çalıştırdım . Bir astarın ortalaması 1420ms iken, for...ofmakinemdeki ortalama 1150ms. Bu önemsiz bir fark değil, ancak sadece bir avuç maç yapıyorsanız tek astar iyi çalışacaktır.

matchAllCaniuse'a bakın


4

Sadece tüm eşleşmelerin konumunu bulmak istiyorsanız, sizi küçük bir kesmeye yönlendirmek istiyorum:

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

Değişken uzunlukta bir RegExp'iniz varsa uygun olmayabilir, ancak bazıları için faydalı olabilir.

Bu, büyük / küçük harfe duyarlıdır. Durum duyarsızlığı için String.toLowerCaseönce işlevi kullanın .


Bence cevabınız en iyisi, çünkü RegExp kullanmak tehlikelidir.
Bharata

1

İşte basit bir kod parçacığı:

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

Tüm cevaplar için teşekkürler. Hepsinden geçtim ve 'iğne' alt dizesinin her oluşumunun ilkine son bir indeks veren bir fonksiyon buldum. Birine yardımcı olma ihtimaline karşı buraya gönderiyorum.

Lütfen bunun yalnızca her oluşumun başlangıcı için orijinal taleple aynı olmadığını unutmayın. Benim kullanım durumuma daha uygun çünkü iğne uzunluğunu korumanıza gerek yok.

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

Aynı karakter dizisini de bulabilecek olan bu çözümü kontrol edin, eksik bir şey olup olmadığını bana bildirin.

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


0

Partiye biraz geç kaldım (neredeyse 10 yıl, 2 ay), ancak gelecekteki kodlayıcılar için bir yol, bunu döngü sırasında ve indexOf()

let haystack = "I learned to play the Ukulele in Lebanon.";
let needle = "le";
let pos = 0; // Position Ref
let result = []; // Final output of all index's.
let hayStackLower = haystack.toLowerCase();

// Loop to check all occurrences 
while (hayStackLower.indexOf(needle, pos) != -1) {
  result.push(hayStackLower.indexOf(needle , pos));
  pos = hayStackLower.indexOf(needle , pos) + 1;
}

console.log("Final ", result); // Returns all indexes or empty array if not found

0

@Jcubic'in cevabını takip edin, çözümü davam için küçük bir kafa karışıklığına neden oldu
Örneğin yerine var result = indexes('aaaa', 'aa')geri dönecek Bu yüzden çözümünü durumuma uyacak şekilde aşağıdaki gibi biraz güncelledim[0, 1, 2][0, 2]

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

Tim'in cevabını tavsiye ederim. Ancak, @blazs tarafından yapılan bu yorum "searchStr = aaa ve str = aaaaaa olduğunu varsayalım. O zaman 4 oluşum bulmak yerine kodunuz yalnızca 2 bulacaktır çünkü döngüde searchStr.length ile atlama yapıyorsunuz." Tim'in koduna, özellikle buradaki şu satıra bakarak: Tim'in kodu, startIndex = index + searchStrLen;aranan dizenin kendi uzunluğu dahilinde olan bir örneğini bulamayacaktır. Bu yüzden, Tim'in cevabını değiştirdim:

function getIndicesOf(searchStr, str, caseSensitive) {
    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 + 1;
    }
    return indices;
}

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

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

Bunun + 1yerine olarak değiştirmek + searchStrLen, eğer bir str aaaave bir searchStr varsa dizin 1'in indices dizisinde olmasına izin verecektir aa.

İkinci cevap

Ayrıca çalışan başka bir kod pasajım var:

function getIndicesOf(searchStr, str, caseSensitive) {
    var startIndex = 0, index, indices = [];
    if (!caseSensitive) {
        str = str.toLowerCase();
        searchStr = searchStr.toLowerCase();
    }
    for (var i=0; i<str.length-1; i++) {
        if (str.substr(i, searchStr.length) == searchStr) {
            indices.push(i);
        }
    }
    return indices;
}

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

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

Bununla birlikte, bu pasajın dezavantajı, birincisinin yerleşik JavaScript işlevini kullandığı için birincisinden biraz daha uzun sürebileceğidir indexOf(), ikincisi ise biraz eski deyim " tekerlek. " Yani, genel olarak, bunun yerine ilk cevabımı tavsiye ederim. Not: Herhangi biri kodun nasıl çalıştığını açıklamak için kodda yorum almak isterse, lütfen bunu söyleyin, isteğe yanıt vermekten memnuniyet duyarım.


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

}

Bu, normal ifadeler yerine başka bir dizenin içindeki bir dizenin oluşumlarını arar.

-1

aşağıdaki kod işi sizin için yapacak:

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

String.prototype.match kullanın .

MDN belgelerinin kendisinden bir örnek:

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

12
Soru, kendilerinin değil, oluşumların endekslerinin nasıl bulunacağıdır !
Luckylooke
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.