在Javascript中统计字符串中字符出现的次数

785
我需要统计一个字符串中某个字符出现的次数。
例如,假设我的���符串包含:
var mainStr = "str1,str2,str3,str4";

我想要找到逗号 , 字符的数量,它是3。以及在逗号分割后的单个字符串的数量,这是4。

我还需要验证每个字符串即str1或str2或str3或str4都不应超过15个字符。


根据下面排名最高的答案,你也可以使用这个在线工具来交叉检查结果:https://magictools.dev/#!/tools/character-occurences - WJA
39个回答

1081

我已经更新了这个答案。使用匹配的想法很好,但速度较慢:

console.log(("str1,str2,str3,str4".match(/,/g) || []).length); //logs 3

console.log(("str1,str2,str3,str4".match(new RegExp("str", "g")) || []).length); //logs 4

如果您事先知道要搜索什么,请使用正则表达式文字,如果不知道,则可以使用RegExp构造函数,并将g标志作为参数传递。
如果没有结果,则match返回null,因此使用|| []
我在2009年提供的最初答案如下。它不必要地创建了一个数组,但是(截至2014年9月)使用分割速度更快。 我对此持矛盾态度,如果我真的需要速度,那么毫无疑问我会使用split,但我更喜欢使用match。
如果您正在寻找逗号:
(mainStr.split(",").length - 1) //3

如果您正在寻找字符串,则需使用 `str`。
(mainStr.split("str").length - 1) //4

在@Lo的答案和我自己愚蠢的性能测试中,至少在Chrome中,split速度更快,但是创建额外的数组似乎并不明智。


11
测试表明,在字符串分割时,Firefox比其他任何浏览器都要快得多。 http://jsperf.com/count-the-number-of-occurances-in-string - vsync
4
我刚刚在 vsync 的 jsperf 上测试了正则表达式,但在 Chrome、Firefox 和 IE 浏览器中,它的速度比较慢。分别是 68%、100% 和 14%。我的电脑是 i7 2600。 - Moss
102
我非常不喜欢只是因为"你更喜欢它"就使用正则表达式的想法。正则表达式有其用途,但通常当存在简单的非正则表达式解决方案时,那就是更好的选择。此外请注意,两种方法都创建了一个数组,所以这也不是使用正则表达式的理由。 - Jasper
8
在这种情况下,我更喜欢它的理由是:将字符串分割成数组以获取出现次数是一种迂回的方式来获取信息。仅因为实现细节不同,将数组拆分开来才更快,而获取匹配数则提高了可读性,意图明显且不会创建和填充未使用的数据结构。 - Bjorn
49
split() 是 JavaScript 中的一个基本工具,概念简单,计算分割次数可以明确意图且易于理解。 - bradw2k
显示剩余13条评论

296

至少有五种方法。最好的选项,也应该是最快的(由于本机RegEx引擎),置于顶部。

第一种方法

("this is foo bar".match(/o/g)||[]).length;
// returns 2

方法二

"this is foo bar".split("o").length - 1;
// returns 2

不建议使用Split,因为它会消耗大量资源。它为每个匹配分配新的'Array'实例。不要尝试在通过FileReader读取大于100MB文件时使用它。您可以使用Chrome 的分析器选项观察确切的资源使用情况。

方法三

    var stringsearch = "o"
       ,str = "this is foo bar";
    for(var count=-1,index=-2; index != -1; count++,index=str.indexOf(stringsearch,index+1) );
// returns 2

方法4

查找单个字符

    var stringsearch = "o"
       ,str = "this is foo bar";
    for(var i=count=0; i<str.length; count+=+(stringsearch===str[i++]));
     // returns 2

第五种方法

元素映射和过滤。由于它需要预先分配资源而不是使用Python中的“生成器”,因此不推荐使用此方法:

    var str = "this is foo bar"
    str.split('').map( function(e,i){ if(e === 'o') return i;} )
                 .filter(Boolean)
    //>[9, 10]
    [9, 10].length
    // returns 2

分享: 我制作了这个代码片段,目前包含8种字符计数方法,所以我们可以直接汇集和分享我们的想法 - 只是为了好玩,也许可以进行一些有趣的基准测试 :)


34
我花了一点时间才明白 ||[] 的作用,但这个答案很棒!对于其他还在迷惑的人来说,如果 match() 没有找到匹配项,则返回 null,而 ||[] 会在 match() 返回 null 时返回长度为0的数组,这意味着 length() 将返回0而不是产生类型错误。 - Nathan
2
Lo Sauer,不用为自己辩护,代码很稳定,我通过自己弄清楚它是如何工作的学到了东西 :) 我更喜欢这种方法,而不是实际标记为答案的方法。如果我们不打算使用结果,就没有必要拆分字符串。 - Nathan
3
你的第三种方法(也是最快的)将错过在字符串开头的匹配项。你可以通过使用do...while循环来修复它: var strsearch = "o", str = "othis is foo bar", index = -1, count = -1; do { index = str.indexOf(strsearch, index+1); count++; } while (index != -1); count - Augustus
1
只需将起始索引设置为“-2”即可,但非常感谢@Augustus。 - Lorenz Lo Sauer
1
我刚刚添加了另一个测试用例,没有所有特殊的自动字符串转换为整数(即+(string === str[i++]]),这是迄今为止最快的:http://jsperf.com/count-the-number-of-characters-in-a-string/15 - PiniH
显示剩余5条评论

29
将此函数添加到字符串原型中:
String.prototype.count=function(c) { 
  var result = 0, i = 0;
  for(i;i<this.length;i++)if(this[i]==c)result++;
  return result;
};

用法:

console.log("strings".count("s")); //2

"stringsstringstrings".count("str") 这个怎么处理? - Toskan
2
@Toskan请看一下OP的问题,它是关于“一个字符”的。我猜这就是为什么参数被称为“c”而不是“s”——因为它是“[c]haracter”,而不是“[s]tring”。大多数其他答案都很糟糕,人们为这个极其简单的任务抛出数组和额外的对象,好像内存分配是免费的。 - Mörre

27

简单地使用 split 函数来查找字符串中某个字符的出现次数。

mainStr.split(',').length // 通过逗号作为分隔符进行拆分后的字符串数量为4

mainStr.split(',').length - 1 // 逗号的数量为3


16

更新:这个方案可能很简单,但并不是最快的。请参见下面的基准测试结果。


令人惊讶的是,在13年内,这个答案还没有出现过。直觉上,它似乎应该是最快的:

const s = "The quick brown fox jumps over the lazy dog.";
const oCount = s.length - s.replaceAll('o', '').length;

如果字符串中只有两种字符,那么这个方法会更快:

const s = "001101001";
const oneCount = s.replaceAll('0', '').length;

基准测试

const { performance } = require('node:perf_hooks');

const ITERATIONS = 10000000;
const TEST_STRING = "The quick brown fox jumps over the lazy dog.";

console.log(ITERATIONS, "iterations");

let sum = 0; // make sure compiler doesn't optimize code out
let start = performance.now();
for (let i = 0; i < ITERATIONS; ++i) {
  sum += TEST_STRING.length - TEST_STRING.replaceAll('o', '').length;
}
let end = performance.now();
console.log("  replaceAll duration", end - start, `(sum ${sum})`);

sum = 0;
start = performance.now();
for (let i = 0; i < ITERATIONS; ++i) {
  sum += TEST_STRING.split('o').length - 1
}
end = performance.now();
console.log("  split duration", end - start, `(sum ${sum})`);

10000 iterations
  replaceAll duration 2.6167500019073486 (sum 40000)
  split duration 2.0777920186519623 (sum 40000)
100000 iterations
  replaceAll duration 17.563208997249603 (sum 400000)
  split duration 8.087624996900558 (sum 400000)
1000000 iterations
  replaceAll duration 128.71587499976158 (sum 4000000)
  split duration 64.15841698646545 (sum 4000000)
10000000 iterations
  replaceAll duration 1223.3415840268135 (sum 40000000)
  split duration 629.1629169881344 (sum 40000000)

1
简单而优雅的解决方案。 - Alok Ranjan
1
这不是最快的方法,实际上与大多数回答相比执行速度最慢。当然,它是一个很好的一行代码,但在性能方面并不是最快的,如果你正在处理一个大字符串,它可能是最慢的。最快的方法实际上是使用 string.split('o').length - 1 - User_coder
@User_coder,感谢你提醒我没有完成我的作业。 - Joe Lapp

14

5
* 字符上的错误 (SyntaxError: nothing to repeat)。 - user669677
1
参数必须是一个正则表达式。所以如果你想计算 * 的数量,你需要发送 '[**]'。 - Gerard ONeill

14

你还可以使用rest参数将字符串转换为元素数组,并使用以下方法操作:

const mainStr = 'str1,str2,str3,str4';
const commas = [...mainStr].filter(l => l === ',').length;

console.log(commas);

const mainStr = 'str1,str2,str3,str4';
const commas = [...mainStr].reduce((a, c) => c === ',' ? ++a : a, 0);

console.log(commas);


这是试图用尽可能多的(实际)努力来解决一个简单问题的尝试吗? - Mörre
1
我的意思是,这是更明智的选择。我不知道性能如何,但理解“过滤此字符串的字母并计算剩余数量”比理解“每次看到此字母时拆分此字符串,然后计算有多少个,并减去一个”的难度要小得多。 - DexieTheSheep

10

这里有一个类似的解决方案,但它使用了 Array.prototype.reduce

function countCharacters(char, string) {
  return string.split('').reduce((acc, ch) => ch === char ? acc + 1: acc, 0)
}

如前所述,String.prototype.split 的性能比 String.prototype.replace 要快得多。


9
如果您使用的是lodash,_.countBy方法可以实现此功能:
_.countBy("abcda")['a'] //2

这种方法也适用于数组:

_.countBy(['ab', 'cd', 'ab'])['ab'] //2

请注意,如果搜索字符串不存在,则返回的是 undefined 而不是 0。 - Jay Wick

7

好的,另一个正则表达式 - 可能不是很快,但比其他方法更简短易读,在我的情况下只需使用'_'进行计数。

key.replace(/[^_]/g,'').length

只需去除所有不属于您字符的内容, 但如果输入为字符串,则看起来不太好看。


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