Як знайти індекси всіх зустрічей однієї рядка в іншій у JavaScript?


105

Я намагаюся знайти позиції всіх входжень рядка в іншій рядку, не залежною від регістру.

Наприклад, з урахуванням рядка:

Я навчився грати на укулеле в Лівані.

і рядок пошуку le, я хочу отримати масив:

[2, 25, 27, 33]

Обидва рядки будуть змінними - тобто я не можу жорстко кодувати їх значення.

Я подумав, що це легке завдання для регулярних виразів, але, побившись деякий час, щоб знайти той, який би спрацював, мені не пощастило.

Я знайшов цей приклад, як це досягти, використовуючи .indexOf(), але, безумовно, повинен бути більш стислий спосіб зробити це?

Відповіді:


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

ОНОВЛЕННЯ

У первісному питанні я не помітив, що рядок пошуку має бути змінною. Я написав ще одну версію для вирішення цієї справи, яка використовує indexOf, тож ви повертаєтесь туди, де почали. Як вказував Вріккен у коментарях, для цього для загального випадку з регулярними виразами вам знадобиться уникнути спеціальних символів регулярних виразів, і тоді я думаю, що рішення регулярного вираження стає більше головним болем, ніж варто.

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
Яким би leтут був змінний рядок? Навіть при використанні new Regexp(str);небезпеки особливі символи ховаються, шукаючи $2.50наприклад. Щось подібне regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$(){}=!<>|:])/g, '\\$1'));було б ближче IMHO. Я не впевнений, чи є у js вбудований механізм виходу з регулярних виразів.
Wrikken

new RegExp(searchStr)було б так, і так, у загальному випадку вам доведеться уникати спеціальних символів. Це насправді не варто робити, якщо вам не потрібен цей рівень загальності.
Тім Даун

1
Чудова відповідь і дуже корисна. Велике спасибі, Тіме!
Bungle

1
Якщо рядок пошуку порожній рядок, ви отримуєте нескінченний цикл ... зробив би перевірку на це.
HelpMeStackOverflowMyOnlyHope

2
Припустимо, searchStr=aaaі те str=aaaaaa. Тоді замість того, щоб знайти 4 випадки, ваш код знайде лише 2, оскільки ви робите пропуски searchStr.lengthв циклі.
блаз

18

Ось безкоштовна версія 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 : і якщо ви хочете зіставити рядки типу "aaaa" та "aa", щоб знайти [0, 2], використовуйте цю версію:

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. Найшвидшим методом був метод за допомогою Regex: jsperf.com/javascript-find-all
StuR

1
Найшвидший метод використовує indexOf jsperf.com/find-o-substrings
Етан Яньцзя Лі Кр

@LiEthan це буде мати значення лише в тому випадку, якщо ця функція є вузьким місцем і, можливо, якщо вхідна рядок довга.
jcubic

@jcubic Ваше рішення здається хорошим, але просто має невелику плутанину. Що робити, якщо я називаю цю функцію var result = indexes('aaaa', 'aa')? Очікуваний результат повинен бути [0, 1, 2]чи [0, 2]?
Cao Mạnh Quang

@ CaoMạnhQuang, дивлячись на код перший результат. Якщо ви хочете другий, вам потрібно створити цикл і всередині, якщо ви помістите i+=find.length;і іншеi++
jcubic

15

Ви впевнені, що можете це зробити!

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

Редагувати: навчитися писати RegExp

Крім того, я зрозумів, що це не зовсім те , що ви хочете, як це lastIndexговорить нам кінець голки не початок, але це близько - ви можете натиснути re.lastIndex-needle.lengthна масив результатів ...

Редагувати: додавання посилання

У відповіді @Tim Down використовується об’єкт результатів з RegExp.exec (), і всі мої ресурси Javascript переглядають його використання (окрім того, що дають вам відповідну рядок). Тож коли він використовує result.index, це якийсь неназваний об'єкт Match. У описі MDC exec вони фактично описують цей об'єкт гідно докладно.


Га! Дякую за внесок у будь-який випадок - я ціную це!
Bungle

9

Один вкладиш з використанням String.protype.matchAll(ES2020):

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

Використовуючи ваші значення:

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]

Якщо ви переживаєте, як зробити спред і map()в один рядок, я провів його for...ofциклом для мільйона ітерацій (використовуючи ваші рядки). Один вкладиш складає в for...ofсередньому 1420 мс, а в середньому 1150 мс на моїй машині. Це не незначна різниця, але один лайнер буде добре працювати, якщо ви робите лише кілька сірників.

Побачити matchAllна канюзі


3

Якщо ви просто хочете знайти позицію всіх матчів, я хотів би вказати вам на невеликий злом:

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

Це може бути застосовно, якщо у вас є RegExp зі змінною довжиною, але для деяких це може бути корисно.

Це враховує регістри. Для нечутливості випадку використовуйте String.toLowerCaseфункцію раніше.


Я думаю, що ваша відповідь найкраща, тому що використовувати RegExp небезпечно.
Бхарата

1

Ось простий Код

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

Дотримуйтесь відповіді @jcubic, його рішення спричинило невелику плутанину в моєму випадку.
Наприклад, var result = indexes('aaaa', 'aa')він повернеться [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

Дякую за всі відповіді. Я переглянув усі вони і придумав функцію, яка дає перший останній індекс кожного виникнення підрядок 'голка'. Я публікую його тут, якщо це комусь допоможе.

Зверніть увагу, він не є таким, як оригінальний запит лише на початок кожної зустрічі. Він краще підходить для мого футляру, тому що вам не потрібно тримати голку.

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

Перевірте це рішення, яке зможе знайти і той самий рядок символів, дайте мені знати, якщо щось не вистачає чи ні.

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;

}

Це шукає входження рядка всередині іншого рядка, а не регулярні вирази.

-1

наведений нижче код зробить роботу для вас:

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 .

Ось приклад із самих документів 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']

Це досить просто.
igaurav

11
Питання полягає в тому, як знайти показники подій, а не події, які вони є собою!
Luckylooke

1
цей відповідь не відповідає питанню, але це те, що я шукав :)
AlexNikonov
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.