我有一个数组:
[1, 2, 3, 5, 2, 8, 9, 2]
我想知道数组中有多少 2
。
在 JavaScript 中不使用 for
循环的最优雅的方法是什么?
原文由 Leem 发布,翻译遵循 CC BY-SA 4.0 许可协议
我有一个数组:
[1, 2, 3, 5, 2, 8, 9, 2]
我想知道数组中有多少 2
。
在 JavaScript 中不使用 for
循环的最优雅的方法是什么?
原文由 Leem 发布,翻译遵循 CC BY-SA 4.0 许可协议
[ 这个答案有点过时:阅读编辑,在 javascript 中“相等”的概念是模棱两可的]
Say hello to your friends: map
and filter
and reduce
and forEach
and every
etc.
(我只是偶尔在 javascript 中编写 for 循环,因为缺少块级范围,所以如果您需要捕获或克隆迭代索引或值,则无论如何都必须使用函数作为循环体。For 循环通常效率更高,但有时您需要关闭。)
最易读的方式:
[....].filter(x => x==2).length
(我们可以写 .filter(function(x){return x==2}).length
代替)
以下是更节省空间的(O(1)而不是 O(N)),但我不确定你可能会在时间方面支付多少好处/惩罚(不超过自访问以来的常数因子每个元素恰好一次):
[....].reduce((total,x) => (x==2 ? total+1 : total), 0)
或者正如评论者善意指出的那样:
[....].reduce((total,x) => total+(x==2), 0)
(如果您需要优化这段特定的代码,for 循环在某些浏览器上可能会更快……您可以在 jsperf.com 上进行测试。)
然后你可以优雅的把它变成一个原型函数:
[1, 2, 3, 5, 2, 8, 9, 2].count(2)
像这样:
Object.defineProperties(Array.prototype, {
count: {
value: function(value) {
return this.filter(x => x==value).length;
}
}
});
您还可以在上述属性定义中使用常规的旧 for 循环技术(参见其他答案)(同样,这可能会更快)。
2017年编辑:
哎呀,这个答案比正确答案更受欢迎。 实际上,只需使用已接受的答案即可。虽然这个答案可能很可爱,但 js 编译器可能不会(或由于规范而不能)优化此类情况。所以你真的应该写一个简单的 for 循环:
Object.defineProperties(Array.prototype, {
count: {
value: function(query) {
/*
Counts number of occurrences of query in array, an integer >= 0
Uses the javascript == notion of equality.
*/
var count = 0;
for(let i=0; i<this.length; i++)
if (this[i]==query)
count++;
return count;
}
}
});
您可以定义一个版本 .countStrictEq(...)
它使用 ===
平等的概念。平等的概念可能对你正在做的事情很重要! (例如 [1,10,3,'10'].count(10)==2
,因为在 javascript 中像 ‘4’==4 这样的数字…因此调用它 .countEq
或 .countNonstrict
强调它使用 ==
运营商。)
警告:在原型上定义一个通用名称应该小心。如果您控制自己的代码,那很好,但如果每个人都想声明自己的
[].count
函数,尤其是当它们的行为不同时,那就不好了。你可能会问自己“但是.count(query)
听起来确实非常完美和规范”……但考虑一下也许你可以做类似[].count(x=> someExpr of x)
的事情。在这种情况下,您可以定义诸如countIn(query, container)
类的函数(在myModuleName.countIn
下),或其他函数,或[].myModuleName_count()
。
还可以考虑使用您自己的多集数据结构(例如像 python 的“ collections.Counter
”)以避免首先进行计数。这适用于形式 [].filter(x=> x==???).length
(最坏情况 O(N) 下降到 O(1) )的精确匹配,并且修改将加速形式查询 [].filter(filterFunction).length
(大致由 #total/#duplicates 的因子)。
class Multiset extends Map {
constructor(...args) {
super(...args);
}
add(elem) {
if (!this.has(elem))
this.set(elem, 1);
else
this.set(elem, this.get(elem)+1);
}
remove(elem) {
var count = this.has(elem) ? this.get(elem) : 0;
if (count>1) {
this.set(elem, count-1);
} else if (count==1) {
this.delete(elem);
} else if (count==0)
throw `tried to remove element ${elem} of type ${typeof elem} from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)`;
// alternatively do nothing {}
}
}
演示:
> counts = new Multiset([['a',1],['b',3]])
Map(2) {"a" => 1, "b" => 3}
> counts.add('c')
> counts
Map(3) {"a" => 1, "b" => 3, "c" => 1}
> counts.remove('a')
> counts
Map(2) {"b" => 3, "c" => 1}
> counts.remove('a')
Uncaught tried to remove element a of type string from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)
旁注:尽管如此,如果您仍然想要函数式编程方式(或不覆盖 Array.prototype 的一次性单行代码),现在您可以将其更简洁地写为 [...].filter(x => x==2).length
。如果您关心性能,请注意虽然这与 for 循环(O(N) 时间)渐进地具有相同的性能,但它可能需要 O(N) 额外内存(而不是 O(1) 内存),因为它几乎当然会生成一个中间数组,然后计算该中间数组的元素。
原文由 ninjagecko 发布,翻译遵循 CC BY-SA 4.0 许可协议
10 回答11.4k 阅读
5 回答4.9k 阅读✓ 已解决
4 回答3.2k 阅读✓ 已解决
2 回答2.9k 阅读✓ 已解决
3 回答2.5k 阅读✓ 已解决
3 回答2.3k 阅读✓ 已解决
2 回答2.7k 阅读✓ 已解决
很简单: