如何计算数组中某些元素的数量?

Lee*_*eem 119 javascript

如果我有一个数组[1, 2, 3, 5, 2, 8, 9, 2],我想检查2数组中有多少个.在不使用for循环循环的情况下,在JavaScript中执行此操作的最优雅方法是什么?

nin*_*cko 227

[ 这个答案有点过时了:阅读编辑 ]

打个招呼给您的朋友:mapfilterreduceforEachevery等.

(我偶尔会在javascript中编写for循环,因为缺少块级别的范围,所以如果你需要捕获或克隆你的迭代索引或值,你必须使用一个函数作为循环的主体.For-loops一般来说效率更高,但有时你需要一个闭包.)

最可读的方式:

[....].filter(x => x==2).length
Run Code Online (Sandbox Code Playgroud)

以下是更节省空间(O(1)而不是O(N)),但我不确定您可以在时间方面支付多少利益/罚款(不超过自您访问以来的常数因素)每个元素恰好一次):

[....].reduce((total,x) => (x==2 ? total+1 : total), 0)
Run Code Online (Sandbox Code Playgroud)

(如果你需要优化这段特殊的代码,在某些浏览器上for循环可能会更快......你可以在jsperf.com上测试一些东西.)


然后,您可以优雅并将其转换为原型函数:

[1, 2, 3, 5, 2, 8, 9, 2].count(2)
Run Code Online (Sandbox Code Playgroud)

像这样:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(value) {
            return this.filter(x => x==value).length;
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

你也可以在上面的属性定义中坚持常规的旧的for-loop技术(参见其他答案)(再次,这可能会更快).


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;
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

您可以定义.filter(function(x){return x==2}).length使用.countStrictEq(...)相等概念的版本.平等的概念可能对你正在做的事情很重要!(例如===,因为javascript中的数字如'4'== 4 ...因此调用它[1,10,3,'10'].count(10)==2.countEq强调它使用.countNonstrict运算符.)

还要考虑使用自己的多集数据结构(例如像python的' ==')来避免首先进行计数.

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 {}
    }
}
Run Code Online (Sandbox Code Playgroud)

演示:

> 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)
Run Code Online (Sandbox Code Playgroud)

旁注:虽然,如果你仍然想要功能编程方式(或者一次性使用而不重写Array.prototype),你现在可以更加简洁地编写它collections.Counter.如果你关心性能,请注意虽然这是渐进式的与for-loop(O(N)时间)相同的性能,但它可能需要O(N)额外的内存(而不是O(1)内存),因为它几乎会当然会生成一个中间数组,然后计算该中间数组的元素.

  • @tokland也许过滤器不会创建中间数组。一个好的优化编译器可以轻松地识别出仅使用数组的长度。也许目前的JS编译器都不具备执行此操作的能力,但这并不重要。正如Fowler所说:“如果有什么伤痛,那就多做些。” 通过编写不良代码来避免编译器不足是短视的。如果编译器很烂,请修复编译器。https://mlafeldt.github.io/blog/if-it-hurts-do-it-more-often/ (2认同)

Tho*_*sen 74

非常简单:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}
Run Code Online (Sandbox Code Playgroud)

  • 不,我的意思是不用"for"循环 (27认同)
  • OP可能认为循环是错误的,因为它是5行代码并且需要可变状态.后来开始阅读的开发人员将不得不花一些时间来检查它的作用,并从他们的任务中失去焦点.抽象是优越的:[`const count = countItems(array,2);`](http://stackoverflow.com/a/6125774/99777),实现细节可以在里面讨论. (22认同)
  • @Leem:为什么循环不好?在某些时候总会出现循环.显然你会创建一个隐藏循环的函数.这些*"我不想使用正确的工具来完成工作"* - 请求对我来说没有多大意义.我们可以争论什么是最优雅的.例如,对于我来说,对每个元素进行函数调用以便将它与值进行比较并不优雅. (6认同)
  • JS .reduce() 仍然使用循环。唯一的区别是循环隐藏在reduce 源代码中,而不是您自己的函数中。 (5认同)
  • 这不是正确的答案,因为该问题明确要求不使用循环。检查我的解决方案,它不使用循环。/sf/answers/3132040551/ (4认同)
  • 笑声:警告(eval('('+ my_array.join('== 2)+(')+'== 2)'))https://jsfiddle.net/gaby_de_wilde/gujbmych/ (2认同)

小智 53

2017: 如果有人仍然对这个问题感兴趣,我的解决方案如下:

const arrayToCount = [1, 2, 3, 5, 2, 8, 9, 2];
const result = arrayToCount.filter(i => i === 2).length;
console.log('number of the found elements: ' + result);
Run Code Online (Sandbox Code Playgroud)


Han*_*son 44

ES6更新到JS:

// Let has local scope
let array = [1, 2, 3, 5, 2, 8, 9, 2]

// Functional filter with an Arrow function
array.filter(x => x === 2).length  // -> 3
Run Code Online (Sandbox Code Playgroud)

JS中的以下一致箭头函数(lambda函数):

(x) => {
   const k = 2
   return k * x
}
Run Code Online (Sandbox Code Playgroud)

可以简化为单个输入的简洁形式:

x => 2 * x
Run Code Online (Sandbox Code Playgroud)

其中return的暗示.


Yan*_*eck 9

这是在 O(N) 中获取所有数组项计数的 ES2017+ 方法:

const arr = [1, 2, 3, 5, 2, 8, 9, 2];
const counts = {};

arr.forEach((el) => {
  counts[el] = counts[el] ? (counts[el] += 1) : 1;
});
Run Code Online (Sandbox Code Playgroud)

您还可以选择对输出进行排序:

const countsSorted = Object.entries(counts).sort(([_, a], [__, b]) => a - b);
Run Code Online (Sandbox Code Playgroud)

console.log(countsSorted) 用于您的示例数组:

[
  [ '2', 3 ],
  [ '1', 1 ],
  [ '3', 1 ],
  [ '5', 1 ],
  [ '8', 1 ],
  [ '9', 1 ]
]
Run Code Online (Sandbox Code Playgroud)


Col*_*man 6

如果您使用lodash或下划线,_.countBy方法将提供由数组中的每个值键入的聚合总计对象.如果您只需要计算一个值,则可以将其转换为单行:

_.countBy(['foo', 'foo', 'bar'])['foo']; // 2
Run Code Online (Sandbox Code Playgroud)

这也适用于数字数组.你的例子的单行将是:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3
Run Code Online (Sandbox Code Playgroud)

  • 巨大的矫枉过正.因为它为所有独特元素创建计数器.浪费的存储和时间. (4认同)
  • 请记住,lodash 总是会增加您的包大小,而且通常相当显着。对于像计算数组项这样简单的事情,我不会使用 lodash。 (2认同)

Gar*_*een 5

我能想到的最奇怪的方法是:

(a.length-(' '+a.join(' ')+' ').split(' '+n+' ').join(' ').match(/ /g).length)+1
Run Code Online (Sandbox Code Playgroud)

在哪里:

  • a是数组
  • n是数组中要计数的数字

我的建议是,使用 while 或 for 循环;-)