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


105

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

例如,给定字符串:

我学会了在黎巴嫩玩四弦琴。

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

[2, 25, 27, 33]

这两个字符串都是变量-即,我无法对它们的值进行硬编码。

我认为对于正则表达式来说这是一件容易的事,但是经过一段时间的努力找到一个可行的表达式后,我却没有运气。

我找到了使用来完成此操作的示例.indexOf(),但是肯定有一种更简洁的方法可以做到这一点吗?

Answers:


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,所以您回到了开始的地方。正如Wrikken在评论中所指出的那样,要对带有正则表达式的一般情况执行此操作,您需要转义特殊的正则表达式字符,这时我认为正则表达式解决方案变得比其价值更令人头疼。

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'));恕我直言。我不确定js是否具有内置的正则表达式转义机制。
2010年

new RegExp(searchStr)就是这样,是的,在一般情况下,您必须转义特殊字符。除非您需要这种一般性,否则这确实不值得做。
Tim Down'8

1
很好的答案,并且非常有帮助。非常感谢,蒂姆!
Bungle 2010年

1
如果搜索字符串为空字符串,则会出现无限循环...将对其进行检查。
HelpMeStackOverflowMyOnlyHope

2
假设searchStr=aaa那个str=aaaaaa。然后,您将不会发现4个事件,而只会发现2个,因为您正在searchStr.length循环中进行跳过。
blazs

18

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

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

7
+1。我进行了一些测试,以与使用Regex的解决方案进行比较。最快的方法是使用Regex的方法:jsperf.com/javascript-find-all
StuR 2013年

1
最快的方法是使用indexOf jsperf.com/find-o-substrings
Ethan

@LiEthan仅在该函数是瓶颈以及输入字符串很长的情况下才重要。
jcubic

@jcubic您的解决方案似乎不错,但是有点困惑。如果我这样调用函数var result = indexes('aaaa', 'aa')怎么办?预期结果应为[0, 1, 2][0, 2]
Cao Mhnh Quang '18

@CaoMạnhQuang查看代码的第一个结果。如果需要第二个,则需要创建while循环,如果要放置i+=find.length;i++
插入

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()中的result对象,我所有的Javascript资源都掩盖了它的使用(除了为您提供匹配的字符串之外)。因此,当他使用时result.index,这就是某种未命名的匹配对象。在execMDC描述中,他们实际上详细描述了该对象。


哈!无论如何,感谢您的贡献-非常感谢!
Bungle 2010年

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()在一个行中进行价差和a for...of运算,那么我会循环运行一百万次迭代(使用您的字符串)。一支班轮平均1420毫秒,而for...of在我的机器上平均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

感谢所有的答复。我仔细研究了所有这些内容,并想出了一个函数,该函数为“ needle”子字符串的每次出现提供第一个最后一个索引。我将其张贴在这里,以防它对某人有所帮助。

请注意,仅在每次出现时才与原始请求不同。因为您不需要保持针的长度,所以它更适合我的用例。

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.