如何在 JavaScript 中找到一个字符串在另一个字符串中所有出现的索引?

Posted

技术标签:

【中文标题】如何在 JavaScript 中找到一个字符串在另一个字符串中所有出现的索引?【英文标题】:How to find indices of all occurrences of one string in another in JavaScript? 【发布时间】:2011-03-25 12:56:14 【问题描述】:

我正在尝试查找一个字符串在另一个字符串中所有出现的位置,不区分大小写。

例如,给定字符串:

我在黎巴嫩学会了弹尤克里里。

和搜索字符串le,我要获取数组:

[2, 25, 27, 33]

两个字符串都是变量——也就是说,我不能硬编码它们的值。

我认为这对于正则表达式来说是一件容易的事,但是在努力寻找一个可行的方法之后,我没有运气。

我找到了this example 了解如何使用.indexOf() 完成此操作,但肯定必须有更简洁的方法吗?

【问题讨论】:

【参考方案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);

更新

我未能在原始问题中发现搜索字符串必须是一个变量。我已经编写了另一个版本来处理使用indexOf 的这种情况,所以你又回到了你开始的地方。正如 Wrikken 在 cmets 中所指出的那样,要对使用正则表达式的一般情况执行此操作,您需要转义特殊的正则表达式字符,此时我认为正则表达式解决方案变得比它的价值更令人头疼。

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>

【讨论】:

le 怎么会是这里的变量字符串?即使使用new Regexp(str);,特殊字符的危险也潜伏着,例如搜索$2.50。像regex = new Regexp(dynamicstring.replace(/([\\.+*?\\[^\\]$()=!<>|:])/g, '\\$1')); 这样的东西会更接近恕我直言。我不确定js是否有内置的正则表达式转义机制。 ... 啊,我明白了:我没有发现 OP 确实需要那种通用性的问题。重写... 很好的答案,非常有帮助。非常感谢,蒂姆! 如果搜索字符串是一个空字符串,你会得到一个无限循环......会检查它。 假设searchStr=aaastr=aaaaaa。然后,您的代码不会找到 4 次出现,而是只会找到 2 次,因为您在循环中通过 searchStr.length 跳过。【参考方案2】:

使用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 循环运行它一百万次迭代(使用您的字符串)。在我的机器上,一个班轮平均为 1420 毫秒,而for...of 平均为 1150 毫秒。这不是一个微不足道的差异,但如果您只进行少数比赛,那么一个班轮就可以正常工作。

See matchAll on caniuse

【讨论】:

【参考方案3】:

这里是正则表达式免费版本:

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

编辑:如果您想匹配 '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;

【讨论】:

+1。我进行了一些测试,以便与使用 Regex 的解决方案进行比较。最快的方法是使用正则表达式的方法:jsperf.com/javascript-find-all 最快的方法是使用 indexOf jsperf.com/find-o-substrings @LiEthan 仅当该函数是瓶颈并且输入字符串很长时才有意义。 @jcubic 您的解决方案看起来不错,但只是有点混乱。如果我调用这样的函数var result = indexes('aaaa', 'aa') 怎么办?预期结果应该是[0, 1, 2][0, 2] @CaoMạnhQuang 查看代码的第一个结果。如果你想要第二个,你需要创建 while 循环,如果你把 i+=find.length; 和 else 在里面 i++【参考方案4】:

你一定能做到!

//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 时,那是某种未命名的匹配对象。在MDC description of exec 中,他们实际上详细描述了这个对象。

【讨论】:

哈!无论如何,感谢您的贡献 - 我很感激!【参考方案5】:

如果您只想找到所有匹配项的位置,我想向您指出一个小技巧:

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 是危险的。【参考方案6】:

我参加聚会有点晚了(将近 10 年零 2 个月),但未来编码人员的一种方法是使用 while 循环和 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

【讨论】:

这是一个很好的解决方案,工作正常。谢谢!【参考方案7】:

这是一个简单的代码sn-p:

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

【讨论】:

【参考方案8】:

我会推荐蒂姆的答案。但是,@blazs 的 this comment 声明“假设 searchStr=aaastr=aaaaaa。然后您的代码不会找到 4 个出现,因为您在循环中通过 searchStr.length 进行了跳过。”,即通过查看 Tim 的代码,特别是此处的这一行,这是真的:startIndex = index + searchStrLen; Tim 的代码将无法找到正在搜索的字符串的实例,该实例在其自身长度内。所以,我修改了蒂姆的回答:

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 searchStr = prompt("Enter a string.");
var str = prompt("What do you want to search for in the string?");
var indices = getIndicesOf(str, searchStr);

document.getElementById("output").innerHTML = indices + "";
&lt;div id="output"&gt;&lt;/div&gt;

如果我的 str 为 aaaaaa 且 searchStr 为 aaa,则将其更改为 + 1 而不是 + searchStrLen 将允许索引 1 位于索引数组中。

附:如果有人希望代码中的 cmets 解释代码是如何工作的,请说出来,我很乐意回复请求。

【讨论】:

【参考方案9】:
const findAllOccurrences = (str, substr) => 
  str = str.toLowerCase();
  
  let result = [];

  let idx = str.indexOf(substr)
  
  while (idx !== -1) 
    result.push(idx);
    idx = str.indexOf(substr, idx+1);
  
  return result;


console.log(findAllOccurrences('I learned to play the Ukulele in Lebanon', 'le'));

【讨论】:

【参考方案10】:

感谢所有回复。我遍历了所有这些,并想出了一个函数,它为第一个给出每次出现的 'needle' substring 的最后一个索引。我在这里发布它以防它对某人有所帮助。

请注意,它与原始请求不同,只是每次出现的开头。它更适合我的用例,因为您不需要保持针的长度。

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

【讨论】:

【参考方案11】:

检查这个解决方案,它也能找到相同的字符串,如果有什么遗漏或不正确,请告诉我。

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

【讨论】:

【参考方案12】:

关注@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;

【讨论】:

【参考方案13】:

这是我的代码(使用搜索和切片方法)

    let s = "I learned to play the Ukulele in Lebanon"
    let sub = 0 
    let matchingIndex = []
    let index = s.search(/le/i)
    while( index >= 0 )
       matchingIndex.push(index+sub);
       sub = sub + ( s.length - s.slice( index+1 ).length )
       s = s.slice( index+1 )
       index = s.search(/le/i)
     
    console.log(matchingIndex)

【讨论】:

【参考方案14】:

这也是我通常用来根据位置获取字符串索引的方法。

我传递以下参数:

search:要搜索的字符串

find:要查找的字符串

位置(默认为'all'):查找字符串在搜索字符串中出现的位置

(如果“全部”返回完整的索引数组)

(如果'last'返回最后一个位置)

function stringIndex (search, find, position = "all") 
    
    var currIndex = 0, indexes = [], found = true;
    
    while (found)         
        var searchIndex = search.indexOf(find);
        if (searchIndex > -1) 
            currIndex += searchIndex + find.length; 
            search = search.substr (searchIndex + find.length);
            indexes.push (currIndex - find.length);
         else found = false; //no other string to search for - exit from while loop   
    
    
    if (position == 'all') return indexes;
    if (position > indexes.length -1) return [];
    
    position = (position == "last") ? indexes.length -1 : position;
    
    return indexes[position];        


//Example:
    
var myString = "Joe meets Joe and together they go to Joe's house";
console.log ( stringIndex(myString, "Joe") ); //0, 10, 38
console.log ( stringIndex(myString, "Joe", 1) ); //10
console.log ( stringIndex(myString, "Joe", "last") ); //38
console.log ( stringIndex(myString, "Joe", 5) ); //[]

【讨论】:

【参考方案15】:

朋友们,这只是另一种使用 reduce 和辅助方法查找匹配短语索引的方法。当然 RegExp 更方便,并且可能在内部以某种方式实现。我希望你觉得它有用。

function findIndexesOfPhraseWithReduce(text, phrase) 
      //convert text to array so that be able to manipulate.
          const arrayOfText = [...text];

      /* this function takes the array of characters and
      the search phrase and start index which comes from reduce method
      and calculates the end with length of the given phrase then slices
      and joins characters and compare it whith phrase.
      and returns True Or False */

         function isMatch(array, phrase, start) 
         const end = start + phrase.length;
         return (array.slice(start, end).join('')).toLowerCase() === 
               phrase.toLowerCase();
         

    /* here we reduce the array of characters and test each character
    with isMach function which takes "current index" and matches the phrase
    with the subsequent character which starts from current index and
    ends at the last character of phrase(the length of phrase). */

        return arrayOfText.reduce((acc, item, index) => isMatch(arrayOfText, phrase, 
        index) ? [...acc, index] : acc, []);


findIndexesOfPhraseWithReduce("I learned to play the Ukulele in Lebanon.", "le");

function findIndexesOfPhraseWithReduce(text, phrase) 
     
         const arrayOfText = [...text];
         function isMatch(array, phrase, start) 
         const end = start + phrase.length;
         return (array.slice(start, end).join('')).toLowerCase() === 
               phrase.toLowerCase();
         
        return arrayOfText.reduce((acc, item, index) => isMatch(arrayOfText, phrase, 
        index) ? [...acc, index] : acc, []);


console.log(findIndexesOfPhraseWithReduce("I learned to play the Ukulele in Lebanon.", "le"));

【讨论】:

【参考方案16】:
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;


【讨论】:

这会查找另一个字符串中出现的字符串,而不是正则表达式。【参考方案17】:

下面的代码将为您完成这项工作:

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

【讨论】:

【参考方案18】:

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

【讨论】:

问题是如何找到出现的索引,而不是它们自身的出现!

以上是关于如何在 JavaScript 中找到一个字符串在另一个字符串中所有出现的索引?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript确定一个字符串是否包含在另一个字符串中的四种方法

如何在另一个 JavaScript 文件中包含一个 JavaScript 文件?

在 React JavaScript 中,如何在另一个类中调用一个类?

给定2个字符串,找到一个字符串出现在另一个字符串中的索引[重复]

如何在另一个 javascript 文件中包含 jquery [重复]

在另一个 js 文件中进行 dom 操作后,如何运行 javascript 文件?