在数组中对属性值求和的更好方法

372

我有这样的东西:

$scope.traveler = [
            {  description: 'Senior', Amount: 50},
            {  description: 'Senior', Amount: 50},
            {  description: 'Adult', Amount: 75},
            {  description: 'Child', Amount: 35},
            {  description: 'Infant', Amount: 25 },
];

现在,为了获得这个数组的总金额,我要做如下操作:

$scope.totalAmount = function(){
       var total = 0;
       for (var i = 0; i < $scope.traveler.length; i++) {
              total = total + $scope.traveler[i].Amount;
            }
       return total;
}

当只有一个数组时,这很容易处理。但我还有其他属性名不同的数组需要求和。

如果我能像这样做就更好了:

$scope.traveler.Sum({ Amount });

但是我不知道如何以这样的方式进行,使我将来可以像这样重复使用它:

$scope.someArray.Sum({ someProperty });

reduce方法非常适合这种情况。这里有一个很棒的解释。 - Josh
28
这段代码使用了数组的map()方法获取每个元素的myProperty属性值,并用reduce()方法对这些值求和。简而言之,它计算了该数组中所有对象的myProperty属性值之和。 - ashleedawg
20个回答

313

我知道这个问题已经有了一个被接受的答案,但我想提供一个使用array.reduce的替代方案,因为对于reduce来说,将数组求和是一个典型的例子:

$scope.sum = function(items, prop){
    return items.reduce( function(a, b){
        return a + b[prop];
    }, 0);
};

$scope.travelerTotal = $scope.sum($scope.traveler, 'Amount');

Fiddle


很棒的答案,是否可以将 $scope.travelerTotal = $scope.sum($scope.traveler, 'Amount'); 放在控制器函数的开头? - Mo.
如果在创建sum函数之后,返回已翻译的文本:是。 - Gruff Bunny
30
2015年10月6日的“踩”有什么原因吗?如果答案有问题,请添加评论,我会进行更正。匿名踩没有任何教育意义。 - Gruff Bunny
2
如果数组中的某个对象中不存在该属性(undefined),则可能导致NaN。我不知道这是否是最佳检查方法,但它适用于我的情况:function(items, prop) { return items.reduce(function(a, b) { if (!isNaN(b[prop])) { return a + b[prop]; } else { return a } }, 0); } - Claytronicon

234
使用解构和reduce方法对Amount进行求和:

使用reduce方法和解构来对Amount进行求和:

const traveler = [
  { description: 'Senior', Amount: 50 },
  { description: 'Senior', Amount: 50 },
  { description: 'Adult', Amount: 75 },
  { description: 'Child', Amount: 35 },
  { description: 'Infant', Amount: 25 },
];

console.log(traveler.reduce((n, {Amount}) => n + Amount, 0));


14
简单易懂,这是我选择的答案。 - Eagle
5
应该把这个作为最佳答案。 - ncesar
3
简单而强大的方法-本应成为所有答案的首选。感谢@patrick。 - Jegan Baskaran
1
真的是一个很棒的解决方案。我稍微修改了一下:(traveler.reduce((n, {Amount}) => n + Number(Amount), 0) - Muhammad Mubashirullah Durrani
1
这终于让我清楚如何正确使用 reduce。谢谢。 - HotFix

219

这只是另一种看法,本地的JavaScript函数MapReduce是为此而构建的(在许多语言中,Map和Reduce是强大的工具)。

var traveler = [{description: 'Senior', Amount: 50},
                {description: 'Senior', Amount: 50},
                {description: 'Adult', Amount: 75},
                {description: 'Child', Amount: 35},
                {description: 'Infant', Amount: 25}];

function amount(item){
  return item.Amount;
}

function sum(prev, next){
  return prev + next;
}

traveler.map(amount).reduce(sum);
// => 235;

// or use arrow functions
traveler.map(item => item.Amount).reduce((prev, next) => prev + next);

注意:通过编写单独的较小功能,我们可以再次利用它们。

// Example of reuse.
// Get only Amounts greater than 0;

// Also, while using Javascript, stick with camelCase.
// If you do decide to go against the standards, 
// then maintain your decision with all keys as in...

// { description: 'Senior', Amount: 50 }

// would be

// { Description: 'Senior', Amount: 50 };

var travelers = [{description: 'Senior', amount: 50},
                {description: 'Senior', amount: 50},
                {description: 'Adult', amount: 75},
                {description: 'Child', amount: 35},
                {description: 'Infant', amount: 0 }];

// Directly above Travelers array I changed "Amount" to "amount" to match standards.

function amount(item){
  return item.amount;
}

travelers.filter(amount);
// => [{description: 'Senior', amount: 50},
//     {description: 'Senior', amount: 50},
//     {description: 'Adult', amount: 75},
//     {description: 'Child', amount: 35}];
//     Does not include "Infant" as 0 is falsey.

4
将"return Number(item.Amount);"翻译为通俗易懂的中文:返回仅包含数字的值。 - xkeshav
4
我只会对reduce函数中变量prev的名称提出异议。对我而言,这暗示着你获取数组中的前一个值。但实际上它是所有先前值的累加。为了更清晰明了,我喜欢使用accumulatoraggregatorsumSoFar - carpiediem
1
我认为这是最好的答案。 - Rodrigo Borba
1
请注意,将可组合函数的.map()和.reduce()调用分开的(有争议的)优点是以性能降低为代价的,因为.map()和.reduce()都会单独迭代整个数组。如果您正在处理大量数据,则最好使用单个for循环而不是分离的数组方法。 - aleph_one

196

更新的答案

由于向数组原型中添加函数存在许多缺点,因此我正在更新这个答案,提供一种替代方法,它保持了与问题中最初请求的语法类似。

class TravellerCollection extends Array {
    sum(key) {
        return this.reduce((a, b) => a + (b[key] || 0), 0);
    }
}
const traveler = new TravellerCollection(...[
    {  description: 'Senior', Amount: 50},
    {  description: 'Senior', Amount: 50},
    {  description: 'Adult', Amount: 75},
    {  description: 'Child', Amount: 35},
    {  description: 'Infant', Amount: 25 },
]);

console.log(traveler.sum('Amount')); //~> 235

原始回答

由于它是一个数组,你可以向数组原型添加一个函数。

traveler = [
    {  description: 'Senior', Amount: 50},
    {  description: 'Senior', Amount: 50},
    {  description: 'Adult', Amount: 75},
    {  description: 'Child', Amount: 35},
    {  description: 'Infant', Amount: 25 },
];

Array.prototype.sum = function (prop) {
    var total = 0
    for ( var i = 0, _len = this.length; i < _len; i++ ) {
        total += this[i][prop]
    }
    return total
}

console.log(traveler.sum("Amount"))

代码演示: http://jsfiddle.net/9BAmj/


1
谢谢@sp00m,现在我已经改用数组.reduce实现了,就像gruff-bunny回答的那样。 - nramirez
如果您需要支持旧版浏览器,请使用polyfill函数:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce - bottens
请不要扩展Array.prototype,因为这可能会在未来破坏您的代码。为什么扩展原生对象是一种不好的做法? - str
@bottens 如果数组中有空对象怎么办? - Obb

140

我总是避免更改原型方法和添加库,所以这是我的解决方案:

使用reduce数组原型方法就足够了。

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);

6
使用 reduce 函数处理对象时,第二个参数(用于确定 a 的初始值)起到了重要的作用:在第一次迭代中,a 不会是 items 数组的第一个对象,而是会被设为 0 - Parziphal
作为一个函数: const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0); 用法:sumBy(traveler, 'Amount') // => 235 - Rafi
11
作为一名老派程序员,reduce语法对我来说看起来非常晦涩。我的大脑仍然更自然地理解这种写法:let total = 0; items.forEach((item) => { total += item.price; }); - AndrWeisR
2
@AndrWeisR,我最喜欢你的解决方案。此外,使用本地语言是一个热门词汇,但很少有人解释它的用途。我根据你的解决方案编写了更基本的代码行,并且它运行得非常好。let total = 0; items.forEach(item => total += item.price) - Ian Poston Framer
谢谢。这是我问题的最佳解决方案。 - Shehan Silva

49

使用MapReduce提高可读性的替代方案:

const traveler = [
    {  description: 'Senior', amount: 50 },
    {  description: 'Senior', amount: 50 },
    {  description: 'Adult', amount: 75 },
    {  description: 'Child', amount: 35 },
    {  description: 'Infant', amount: 25 },
];

const sum = traveler
  .map(item => item.amount)
  .reduce((prev, curr) => prev + curr, 0);

可重复使用的函数:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);

完美的搭档,"+1" - Mayank Pandeyz
使用reduce默认值 .reduce(*** => *** + ***, 0); 其他人缺少了 "+1"。 - FDisk

26

TypeScriptJavaScript 中对我有效:

let lst = [
     { description:'Senior', price: 10},
     { description:'Adult', price: 20},
     { description:'Child', price: 30}
];
let sum = lst.map(o => o.price).reduce((a, c) => { return a + c });
console.log(sum);

我希望这对你有所帮助。


23

你可以进行以下操作:

$scope.traveler.map(o=>o.Amount).reduce((a,c)=>a+c);

22

我想在这里提供我的意见:这是一种应该始终是纯函数的操作,不依赖于任何外部变量。已经有一些人给出了很好的答案,使用reduce是这里的正确方法。

由于我们大多数人已经可以使用ES2015语法,这是我的建议:

const sumValues = (obj) => Object.keys(obj).reduce((acc, value) => acc + obj[value], 0);
我们一起将它变成一个不可变函数。这里reduce的作用很简单:从累加器的值为0开始,并将当前循环项的值添加到其中。
功能性编程和ES2015太棒了! :)

17

我不确定这是否已经提到了。但是有一个lodash函数可以实现这个功能。以下是代码片段,其中'value'是您要求和的属性。

_.sumBy(objects, 'value');
_.sumBy(objects, function(o) { return o.value; });

都可以使用。


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