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

IT技术 javascript
2021-02-09 21:59:59

我有一个数组:

[1, 2, 3, 5, 2, 8, 9, 2]

我想知道2数组中有多少s。

在没有循环的情况下,在 JavaScript 中最优雅的方法是什么for

6个回答

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

向你的朋友问好:mapand filterand reduceand forEachand everyetc.

(我只是偶尔在 javascript 中编写 for 循环,因为缺少块级作用域,所以如果你需要捕获或克隆你的迭代索引或值,无论如何你必须使用一个函数作为循环体。For-loops通常更有效,但有时你需要一个闭包。)

最易读的方式:

[....].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)

(如果您需要优化这段特定的代码,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年编辑

oop,这个答案比正确答案更受欢迎。实际上,只需使用已接受的答案即可。虽然这个答案可能很可爱,但 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)(under 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) 内存),因为它几乎当然生成一个中间数组,然后计算该中间数组的元素。

@tokland:如果这是一个问题,你可以这样做 array.reduce(function(total,x){return x==value? : total+1 : total}, 0)
2021-03-12 21:59:59
我认为这是 2017 年的最佳解决方案:const count = (list) => list.filter((x) => x == 2).length. 然后通过调用count(list)where list 是一个数字数组来使用它您还可以const count = (list) => list.filter((x) => x.someProp === 'crazyValue').length对对象数组中的 crazyValue 实例进行计数。请注意,它与属性完全匹配。
2021-03-19 21:59:59
这是一个很好的 FP 解决方案,唯一的“问题”(与大多数情况无关)它创建了一个中间数组。
2021-03-31 21:59:59
@ninjagecko 三元运算符中不应该只有一个冒号吗? [...].reduce(function(total,x){return x==2 ? total+1 : total}, 0)
2021-04-03 21:59:59
@tokland 也许过滤器不会创建中间数组。一个好的优化编译器可以很容易地识别出只使用了数组的长度。也许当前的 JS 编译器都不够聪明来做到这一点,但这并不重要。正如 Fowler 所说,“如果有什么伤害了,那就多做一些”。通过编写糟糕的代码来避免编译器缺陷是短视的。如果编译器很烂,请修复编译器。mlafeldt.github.io/blog/if-it-hurts-do-it-more-often
2021-04-04 21:59:59

现代 JavaScript:

请注意,===在 JavaScript (JS) 中进行比较时,应始终使用三重等号三重等于确保 JS 比较==在其他语言中表现得像双重等于(有一个例外,见下文)。以下解决方案显示了如何以功能方式解决此问题,这将永远不会有out of bounds error

// 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

JavaScript 中的以下匿名箭头函数(lambda 函数):

(x) => {
   const k = 2
   return k * x
}

对于单个输入,可以简化为这种简洁的形式:

x => 2 * x

其中return的暗示。

始终使用三重等号:===用于 JS 中的比较,但检查可空性时除外:if (something == null) {}因为您包括检查undefined是否只使用双等号,如本例。

@Niklas,我认为它是相同的(因为两者都必须检查所有元素,O(N)),但是,我想这取决于浏览器以及元素和计算机的数量,以了解适合缓存的内容。所以,我想答案是:“这很复杂”:)
2021-03-15 21:59:59
过滤器功能是否比使用 es6 for of 循环性能更高?
2021-03-18 21:59:59

很简单的:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}
@Leem:为什么循环不好?在某些时候总是循环。显然,您将创建一个隐藏循环的函数。这些“我不想使用正确的工具来完成工作”——对我来说从来没有多大意义。我们可以争论什么是最优雅的。例如,对我来说,对每个元素进行函数调用以将其与值进行比较并不优雅。
2021-03-13 21:59:59
笑声: alert(eval('('+my_array.join('==2)+(')+'==2)')) jsfiddle.net/gaby_de_wilde/gujbmych
2021-03-27 21:59:59
这不是正确的答案,因为该问题明确要求不使用循环。检查我的不使用循环的解决方案。stackoverflow.com/a/44743436/8211014
2021-04-02 21:59:59
OP 可能认为循环很糟糕,因为它有 5 行代码并且需要可变状态。稍后阅读该内容的开发人员将不得不花一些时间来检查它的作用,从而使他们无法专注于他们的任务。抽象要优越得多:const count = countItems(array, 2);实现细节可以在内部争论。
2021-04-06 21:59:59
不,我的意思是不使用“for”循环
2021-04-09 21:59:59

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

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

_.countBy(['foo', 'foo', 'bar'])['foo']; // 2

这也适用于数字数组。您的示例的单行是:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3
请记住,lodash 总是会增加您的包大小,而且通常非常显着。对于像计算数组项这样简单的事情,我不会使用 lodash。
2021-03-28 21:59:59
巨大的矫枉过正。因为它为所有唯一元素创建计数器。浪费存储和时间。
2021-04-05 21:59:59