Bagaimana menemukan indeks dari semua kemunculan satu string di lainnya di JavaScript?

105

Saya mencoba untuk menemukan posisi dari semua kemunculan string dalam string lain, case-insensitive.

Misalnya, diberikan string:

Saya belajar memainkan Ukulele di Lebanon.

dan string pencarian le, saya ingin mendapatkan array:

[2, 25, 27, 33]

Kedua string tersebut akan menjadi variabel - yaitu, saya tidak dapat membuat kode keras nilainya.

Saya pikir ini adalah tugas yang mudah untuk ekspresi reguler, tetapi setelah berjuang beberapa saat untuk menemukan yang akan berhasil, saya tidak beruntung.

Saya menemukan contoh cara melakukannya dengan menggunakan .indexOf(), tetapi tentunya harus ada cara yang lebih ringkas untuk melakukannya?

Mengerjakan dgn kurang baik
sumber

Jawaban:

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

MEMPERBARUI

Saya gagal menemukan pertanyaan awal bahwa string pencarian harus berupa variabel. Saya telah menulis versi lain untuk menangani kasus yang menggunakan ini indexOf, jadi Anda kembali ke tempat Anda memulai. Seperti yang ditunjukkan oleh Wrikken di komentar, untuk melakukan ini untuk kasus umum dengan ekspresi reguler, Anda perlu melepaskan karakter regex khusus, di mana saya pikir solusi regex menjadi lebih memusingkan daripada nilainya.

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
sumber
2
Bagaimana lemenjadi string variabel di sini? Bahkan saat menggunakan new Regexp(str);bahaya karakter khusus mengintai, mencari $2.50misalnya. Sesuatu seperti regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));akan IMHO lebih dekat. Saya tidak yakin apakah js memiliki mekanisme pelolosan regex bawaan.
Wrikken
new RegExp(searchStr)akan menjadi jalannya, dan ya, dalam kasus umum Anda harus melarikan diri dari karakter khusus. Ini tidak benar-benar layak dilakukan kecuali Anda membutuhkan tingkat keumuman itu.
Tim Down
1
Jawaban yang bagus, dan sangat membantu. Terima kasih banyak, Tim!
Bungle
1
Jika string pencarian adalah string kosong Anda mendapatkan loop tak terbatas ... akan melakukan pemeriksaan untuk itu.
HelpMeStackOverflowMyOnlyHope
2
Seandainya searchStr=aaadan itu str=aaaaaa. Kemudian alih-alih menemukan 4 kejadian, kode Anda hanya akan menemukan 2 karena Anda membuat lompatan searchStr.lengthdalam loop.
berkobar
18

Ini adalah versi gratis 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 : dan jika Anda ingin mencocokkan string seperti 'aaaa' dan 'aa' untuk menemukan [0, 2] gunakan versi ini:

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
sumber
7
+1. Saya menjalankan beberapa tes untuk perbandingan terhadap solusi menggunakan Regex. Metode tercepat adalah yang menggunakan Regex: jsperf.com/javascript-find-all
StuR
1
Metode tercepat adalah menggunakan indexOf jsperf.com/find-o-substrings
Ethan Yanjia Li
@LiEthan hanya akan menjadi masalah jika fungsi tersebut mengalami bottleneck dan mungkin jika string inputnya panjang.
jcubic
@jcubic Solusi Anda tampaknya bagus, tetapi hanya sedikit membingungkan. Bagaimana jika saya menyebut fungsi seperti ini var result = indexes('aaaa', 'aa')? Hasil yang diharapkan harus [0, 1, 2]atau [0, 2]?
Cao Mạnh Quang
@ CaoMạnhQuang melihat kode hasil pertama. Jika Anda menginginkan yang kedua, Anda perlu membuat while loop dan di dalam jika Anda meletakkan i+=find.length;dan di laini++
jcubic
15

Anda pasti bisa melakukan ini!

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

Edit: belajar mengeja RegExp

Juga, saya menyadari ini bukan persis apa yang Anda inginkan, lastIndexmemberitahu kita akhir jarum tidak awal, tapi dekat - Anda bisa mendorong re.lastIndex-needle.lengthke dalam hasil array yang ...

Edit: menambahkan link

Jawaban @ Tim Down menggunakan objek hasil dari RegExp.exec (), dan semua sumber daya Javascript saya mengabaikan penggunaannya (selain memberi Anda string yang cocok). Jadi saat dia menggunakan result.index, itu semacam Objek Pencocokan yang tidak disebutkan namanya. Dalam deskripsi MDC tentang exec , mereka benar-benar mendeskripsikan objek ini dengan detail yang layak.

Ryley
sumber
Ha! Bagaimanapun, terima kasih telah berkontribusi - saya menghargainya!
Bungle
9

Satu liner menggunakan String.protype.matchAll(ES2020):

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

Menggunakan nilai-nilai Anda:

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]

Jika Anda khawatir tentang melakukan penyebaran dan map()dalam satu baris, saya menjalankannya dengan satu for...ofputaran untuk satu juta iterasi (menggunakan string Anda). Rata-rata satu liner 1420ms sedangkanfor...of rata rata 1150ms di mesin saya. Itu bukan perbedaan yang signifikan, tetapi satu liner akan berfungsi dengan baik jika Anda hanya melakukan beberapa pertandingan.

Lihat matchAlldi caniuse

Benny Hinrichs
sumber
3

Jika Anda hanya ingin menemukan posisi semua pertandingan, saya ingin mengarahkan Anda ke sedikit retasan:

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

Ini mungkin tidak dapat diterapkan jika Anda memiliki RegExp dengan panjang variabel tetapi untuk beberapa itu mungkin membantu.

Ini peka huruf besar / kecil. Untuk kasus ketidakpekaan, gunakan String.toLowerCasefungsi sebelumnya.

Hoffmann
sumber
Saya pikir jawaban Anda adalah yang terbaik, karena menggunakan RegExp berbahaya.
Bharata
1

Berikut adalah Kode sederhana

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

Ikuti jawaban @jcubic, solusinya menyebabkan sedikit kebingungan untuk kasus saya
Misalnya var result = indexes('aaaa', 'aa')akan kembali [0, 1, 2]daripada [0, 2]
Jadi saya memperbarui sedikit solusinya seperti di bawah ini agar sesuai dengan kasus saya

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

Terima kasih atas semua balasannya. Saya memeriksa semuanya dan menemukan fungsi yang memberikan indeks terakhir pertama dari setiap kemunculan substring 'jarum'. Saya mempostingnya di sini seandainya itu akan membantu seseorang.

Harap dicatat, ini tidak sama dengan permintaan awal hanya untuk permulaan dari setiap kejadian. Ini lebih cocok untuk kasus penggunaan saya karena Anda tidak perlu menjaga panjang jarum.

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

Periksa solusi ini yang juga dapat menemukan string karakter yang sama, beri tahu saya jika ada yang kurang atau tidak benar.

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
sumber
-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
sumber
Ini mencari kemunculan string di dalam string lain daripada ekspresi reguler.
-1

kode di bawah ini akan melakukan pekerjaan untuk Anda:

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

Gunakan String.prototype.match .

Berikut adalah contoh dari dokumen MDN itu sendiri:

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.dll
sumber
Ini sangat mudah.
igaurav
11
Pertanyaannya adalah bagaimana menemukan indeks kemunculan, bukan kemunculannya sendiri!
Luckylooke
1
meskipun jawaban ini tidak cocok dengan pertanyaannya, tapi itulah yang saya cari :)
AlexNikonov