如何统计字符串在另一个字符串中出现的次数?

839

我该如何在一个字符串中统计另一个特定字符串出现的次数?例如,我想在Javascript中实现以下操作:

var temp = "This is a string.";
alert(temp.count("is")); //should output '2'

23
这取决于你是否接受重叠实例,例如:var t = "sss";上述字符串中子串"ss"出现了几次?是1次还是2次?你是跳过每个实例,还是逐个字符地移动指针来寻找子串呢? - Tim
4
这个问题的改进基准:http://jsperf.com/string-ocurrence-split-vs-match/2 (基于Kazzkiq的基准测试)。 - idmean
在 JavaScript 中计算字符串中特定单词的总数 https://stackoverflow.com/a/65036248/4752258 - Farbod Aprin
这个视频似乎与此相关 - “Google编程面试与Facebook软件工程师” - https://www.youtube.com/watch?v=PIeiiceWe_w - Deryck
41个回答

1379

g在正则表达式中代表全局匹配(即global),表示搜索整个字符串而不仅仅是找到第一个匹配项。这将匹配两次is

var temp = "This is a string.";
var count = (temp.match(/is/g) || []).length;
console.log(count);

如果没有匹配项,则返回0

var temp = "Hello World!";
var count = (temp.match(/is/g) || []).length;
console.log(count);


132
谢谢。我选择了count = (str.match(/is/g) || []).length来处理如果没有匹配的情况。 - Matt

278
/** Function that count occurrences of a substring in a string;
 * @param {String} string               The string
 * @param {String} subString            The sub string to search for
 * @param {Boolean} [allowOverlapping]  Optional. (Default:false)
 *
 * @author Vitim.us https://gist.github.com/victornpb/7736865
 * @see Unit Test https://jsfiddle.net/Victornpb/5axuh96u/
 * @see https://dev59.com/eW865IYBdhLWcg3wEKOZ#7924240
 */
function occurrences(string, subString, allowOverlapping) {

    string += "";
    subString += "";
    if (subString.length <= 0) return (string.length + 1);

    var n = 0,
        pos = 0,
        step = allowOverlapping ? 1 : subString.length;

    while (true) {
        pos = string.indexOf(subString, pos);
        if (pos >= 0) {
            ++n;
            pos += step;
        } else break;
    }
    return n;
}

用法

occurrences("foofoofoo", "bar"); //0

occurrences("foofoofoo", "foo"); //3

occurrences("foofoofoo", "foofoo"); //1

允许重叠

occurrences("foofoofoo", "foofoo", true); //2

匹配:

  foofoofoo
1 `----´
2    `----´

单元测试

基准测试

我做了一个基准测试,我的函数比gumbo发布的正则表达式匹配函数快10倍以上。在我的测试字符串中,长度为25个字符,包含2个字符“o”。我在Safari浏览器上执行1,000,000次。

Safari 5.1

Benchmark> 总执行时间:5617毫秒(正则表达式)

Benchmark> 总执行时间:881毫秒(我的函数6.4倍快)

Firefox 4

Benchmark> 总执行时间:8547毫秒(正则表达式)

Benchmark> 总执行时间:634毫秒(我的函数13.5倍快)


编辑:我所做的更改

  • 缓存子串长度

  • 将类型转换为字符串

  • 添加可选参数“allowOverlapping”

  • 修复空子串的正确输出。

Gist代码片段


1
我在这个主题中尝试了不同的答案,而这个答案在大量数据时表现出了约5-10%的更好性能(与其他答案相比)。 - T.Todua

212

function countInstances(string, word) {
   return string.split(word).length - 1;
}
console.log(countInstances("This is a string", "is"))


对我来说,没有-1也能正常工作。 - Ast
使用 https://jsbench.me/ 进行基准测试,与 Vitim.us 的“occurrences”解决方案相比,性能几乎相同。简单的“split”是我首选的解决方案,因为它是最简单的。正则表达式稍微慢一些。 - Felix Furtmayr
2
@Ast 如果没有 -1,计数会出错。 - Welcor

113
你可以尝试这个:

var theString = "This is a string.";
console.log(theString.split("is").length - 1);


43

我的解决方案:

var temp = "This is a string.";

function countOccurrences(str, value) {
  var regExp = new RegExp(value, "gi");
  return (str.match(regExp) || []).length;
}

console.log(countOccurrences(temp, 'is'));


5
也许更好的方法是返回 (str.match(regExp) || []).length; 这样就不会评估两次正则表达式了? - aikeru
4
您还需要对字符串进行转义,或者说countOcurrences('Hello...','.')==8而不是3。 - Vitim.us

19

您可以使用match来定义这样的函数:

String.prototype.count = function(search) {
    var m = this.match(new RegExp(search.toString().replace(/(?=[.\\+*?[^\]$(){}\|])/g, "\\"), "g"));
    return m ? m.length:0;
}

1
如果你想让它与JS的搜索语义保持一致,返回行应该是 return m ? m.length:-1; - Conor O'Brien

14

13

非正则表达式版本:

 var string = 'This is a string',
    searchFor = 'is',
    count = 0,
    pos = string.indexOf(searchFor);

while (pos > -1) {
    ++count;
    pos = string.indexOf(searchFor, ++pos);
}

console.log(count);   // 2


  1. 这仅适用于单个字符搜索,太微妙了。
  2. 即使操作员要求出现“is”的次数。
- vladkras
2
这可能是这里最快的实现,但如果您用“pos+=searchFor.length”替换“++pos”,它会更快。 - hanshenrik

12

String.prototype.Count = function (find) {
    return this.split(find).length - 1;
}

console.log("This is a string.".Count("is"));

这将返回2。


7
这是最快的函数!
为什么它更快?
  • Doesn't check char by char (with 1 exception)
  • Uses a while and increments 1 var (the char count var) vs. a for loop checking the length and incrementing 2 vars (usually var i and a var with the char count)
  • Uses WAY less vars
  • Doesn't use regex!
  • Uses an (hopefully) highly optimized function
  • All operations are as combined as they can be, avoiding slowdowns due to multiple operations

    String.prototype.timesCharExist=function(c){var t=0,l=0,c=(c+'')[0];while(l=this.indexOf(c,l)+1)++t;return t};
    

这里是一种更慢、更易读的版本:

    String.prototype.timesCharExist = function ( chr ) {
        var total = 0, last_location = 0, single_char = ( chr + '' )[0];
        while( last_location = this.indexOf( single_char, last_location ) + 1 )
        {
            total = total + 1;
        }
        return total;
    };

这段代码因为计数器、变量名过长以及一个变量的滥用而导致速度较慢。
要使用它,只需这样做:
    'The char "a" only shows up twice'.timesCharExist('a');

编辑: (2013/12/16)

不要在Opera 12.16或更早版本中使用!它比正则表达式解决方案慢近2.5倍!

在Chrome上,这个解决方案将花费14毫秒至20毫秒处理100万个字符。

对于相同数量的字符,正则表达式解决方案需要11-14毫秒。

使用一个函数(在String.prototype之外)大约需要10-13毫秒。

以下是所使用的代码:

    String.prototype.timesCharExist=function(c){var t=0,l=0,c=(c+'')[0];while(l=this.indexOf(c,l)+1)++t;return t};

    var x=Array(100001).join('1234567890');

    console.time('proto');x.timesCharExist('1');console.timeEnd('proto');

    console.time('regex');x.match(/1/g).length;console.timeEnd('regex');

    var timesCharExist=function(x,c){var t=0,l=0,c=(c+'')[0];while(l=x.indexOf(c,l)+1)++t;return t;};

    console.time('func');timesCharExist(x,'1');console.timeEnd('func');

所有解决方案的结果应该是 100,000!
注:如果你想让这个函数计算多于 1 个字符,请将 c=(c+'')[0] 改为 c=c+''

1
原型仅仅是一个例子!你可以随意使用该函数!你甚至可以这样做:var timesFunctionExist=function(x,c){var t=0,l=0,c=(c+'')[0];while(l=x.indexOf(c,l)+1)++t;return t}); alert(timesCharExist('The char "a" only shows up twice','a'));! (这将进一步加速,因为我不会搞砸原型)。如果你认为我错了,为什么不先展示出来而不是抛石头?向我证明我的函数很烂,我会接受。给我展示一个测试案例。变量的长度确实对速度有影响。你可以测试它。 - Ismael Miguel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接