360

I have something like this:

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

Now to have a total Amount of this array I'm doing something like this:

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

It's easy when is only one array, but I have others arrays with a different property name that I would like to sum.

I would be happier If I could do something like this:

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

But I don't know how to go through this in a way that I could reuse it in the future like this:

$scope.someArray.Sum({ someProperty });
Ivar
  • 6,138
  • 12
  • 49
  • 61
nramirez
  • 5,230
  • 2
  • 23
  • 39
  • The reduce method is perfect for this. Here is a great [explainer](https://www.digitalocean.com/community/tutorials/js-finally-understand-reduce). – Josh Jan 02 '21 at 11:13
  • 24
    ***TL;dr :*** `myArray.map(i=>i.myProperty).reduce((a,b)=>a+b);` – ashleedawg Nov 15 '21 at 23:05

20 Answers20

311

I know that this question has an accepted answer but I thought I'd chip in with an alternative which uses array.reduce, seeing that summing an array is the canonical example for 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

Gruff Bunny
  • 27,738
  • 10
  • 72
  • 59
  • brilliant answer, is it possible to put `$scope.travelerTotal = $scope.sum($scope.traveler, 'Amount');` at the beginning of controller function ? – Mo. Dec 03 '14 at 10:18
  • Yes if it comes after the sum function is created. – Gruff Bunny Dec 03 '14 at 10:39
  • 2
    This can result in NaN, if the prop does not exist (undefined) in one of the objects in the array. I don't know if this is the best check, but it worked for my case: function(items, prop) { return items.reduce(function(a, b) { if (!isNaN(b[prop])) { return a + b[prop]; } else { return a } }, 0); } – Claytronicon Oct 31 '17 at 22:00
218

Just another take, this is what native JavaScript functions Map and Reduce were built for (Map and Reduce are powerhouses in many languages).

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

Note: by making separate smaller functions we get the ability to use them again.

// 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.
SoEzPz
  • 14,958
  • 8
  • 61
  • 64
  • 4
    `return Number(item.Amount);` to check for number only – xkeshav Aug 23 '17 at 06:14
  • 4
    I'd only argue with the name of the variable `prev` in the reduce function. To me, that implies that you're getting the previous value in the array. But really it's the reduction of all previous values. I like `accumulator`, `aggregator` or `sumSoFar` for clarity. – carpiediem Jan 25 '19 at 12:36
  • 1
    I do think this is the best anwser. – Rodrigo Borba Jul 27 '20 at 23:03
  • 1
    Note that the (arguable) advantages in separating the .map() and reduce() calls of having composable functions come at the cost of decreased performance because both `.map()` and `.reduce()` will separately iterate through the entire array. If you're processing large volumes of data, you would be better off using a single for-loop than separate array methods – aleph_one Nov 10 '21 at 01:03
215

Use reduce with destructuring to sum 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));
xinthose
  • 3,213
  • 3
  • 40
  • 59
Patrick Leib
  • 2,151
  • 1
  • 7
  • 4
196

Updated Answer

Due to all the downsides of adding a function to the Array prototype, I am updating this answer to provide an alternative that keeps the syntax similar to the syntax originally requested in the question.

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

Original Answer

Since it is an array you could add a function to the Array prototype.

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

The Fiddle: http://jsfiddle.net/9BAmj/

bottens
  • 3,834
  • 1
  • 13
  • 15
  • 1
    thanks @sp00m now I have changed my implementation with array.reduce just like gruff-bunny answered. – nramirez Apr 23 '14 at 18:49
  • You may need to polyfill the reduce function if you need to support older browsers: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce – bottens Apr 23 '14 at 18:50
  • Please don't extend `Array.prototype` as it might break your code in the future. [Why is extending native objects a bad practice?](https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice). – str Mar 05 '19 at 13:07
  • @bottens what if there is a null object in the array? – Obb Jul 11 '20 at 21:23
139

I always avoid changing prototype method and adding library so this is my solution:

Using reduce Array prototype method is sufficient

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);
Eric Marcelino
  • 1,588
  • 1
  • 7
  • 11
  • 6
    The second parameter (which determines the initial value of `a`) does the trick when using `reduce` with objects: in the first iteratorion, `a` won't be the first object of the `items` array, instead it will be `0`. – Parziphal Jul 12 '18 at 23:32
  • As a function: `const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0);` Usage: `sumBy(traveler, 'Amount') // => 235` – Rafi Nov 29 '18 at 09:58
  • 11
    As an old school programmer, the `reduce` syntax looks totally obtuse to me. My brain still "natively" understands this better: `let total = 0; items.forEach((item) => { total += item.price; });` – AndrWeisR Jan 24 '19 at 01:01
  • 2
    @AndrWeisR I like your solution best. Also the use of native as it's such a buzz word that hardly gets explained what it's meant for. I made an even more basic line of code based on your solution and it worked great. `let total = 0; items.forEach(item => total += item.price)` – Ian Poston Framer Jan 26 '19 at 23:03
  • Thank you. this is best solution for my problem. – Shehan Silva Jan 28 '22 at 19:08
49

Alternative for improved readability and using Map and Reduce:

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

Re-useable function:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);
codingbamby
  • 491
  • 4
  • 3
25

It's working for me in TypeScript and JavaScript:

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

I hope is useful.

AminRostami
  • 2,585
  • 3
  • 29
  • 45
23

You can do the following:

$scope.traveler.map(o=>o.Amount).reduce((a,c)=>a+c);
Boris K
  • 1,469
  • 9
  • 29
greenif
  • 1,055
  • 1
  • 12
  • 25
22

I thought I'd drop my two cents on this: this is one of those operations that should always be purely functional, not relying on any external variables. A few already gave a good answer, using reduce is the way to go here.

Since most of us can already afford to use ES2015 syntax, here's my proposition:

const sumValues = (obj) => Object.keys(obj).reduce((acc, value) => acc + obj[value], 0);

We're making it an immutable function while we're at it. What reduce is doing here is simply this: Start with a value of 0 for the accumulator, and add the value of the current looped item to it.

Yay for functional programming and ES2015! :)

Ricardo Magalhães
  • 1,843
  • 15
  • 10
17

I'm not sure this has been mentioned yet. But there is a lodash function for that. Snippet below where value is your attribute to sum is 'value'.

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

Both will work.

Liam Middleton
  • 280
  • 3
  • 7
6

can also use Array.prototype.forEach()

let totalAmount = 0;
$scope.traveler.forEach( data => totalAmount = totalAmount + data.Amount);
return totalAmount;
akhouri
  • 3,065
  • 3
  • 25
  • 29
6

From array of objects

function getSum(array, column)
  let values = array.map((item) => parseInt(item[column]) || 0)
  return values.reduce((a, b) => a + b)
}

foo = [
  { a: 1, b: "" },
  { a: null, b: 2 },
  { a: 1, b: 2 },
  { a: 1, b: 2 },
]

getSum(foo, a) == 3
getSum(foo, b) == 6
Franz
  • 77
  • 1
  • 7
5

You can use Array.prototype.reduce:

const sum = traveler.reduce((acc , val)=>{
   return acc + val.amount;
} ,0);
Devan Buggay
  • 2,717
  • 4
  • 26
  • 35
nikita shinde
  • 47
  • 1
  • 1
4

Here is a one-liner using ES6 arrow functions.

const sumPropertyValue = (items, prop) => items.reduce((a, b) => a + b[prop], 0);

// usage:
const cart_items = [ {quantity: 3}, {quantity: 4}, {quantity: 2} ];
const cart_total = sumPropertyValue(cart_items, 'quantity');
Steve Breese
  • 792
  • 7
  • 8
4

After going through these answers I think that actually a for (or forEach or for of with await) loop is much more readable that than reduce or even map and reduce. Think of:

  1. coming back to this code after 6 months or maintaining this by someone else. I think your approach of using a loop is good enough.
  2. extending this function in the future, in case you might want to add a currency conversion or similar. Doing this in a one-liner is not a great idea.

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

var sumFromArray = (propertyName, array) => {
  let sum = 0;
  array.forEach(item => {
    sum += item[propertyName] ?? 0;
  });
  return sum;
};

var sumOfTraveler = sumFromArray('Amount', traveler);
console.log(sumOfTraveler);

Using types your function definition might look like:

const sumFromArray = (propertyName: string, array: Array<{[propertyName: string]: number}>) => { ... };

See here for more details: TypeScript A computed property name in a type literal must directly refer to a built-in symbol

I have nothing against map, reduce or one-liners, this is just food for thought.

lukas_o
  • 3,776
  • 4
  • 34
  • 50
1

How to sum array of object using Javascript

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

const traveler = [
    {  description: 'Senior', Amount: 50},
    {  description: 'Senior', Amount: 50},
    {  description: 'Adult', Amount: 75},
    {  description: 'Child', Amount: 35},
    {  description: 'Infant', Amount: 25 },
];
function sum(arrayData, key){
   return arrayData.reduce((a,b) => {
  return {Amount : a.Amount + b.Amount}
})
}
console.log(sum(traveler))
`
mx0
  • 6,445
  • 12
  • 49
  • 54
kvimalkr55
  • 53
  • 8
1

Here's a solution I find more flexible:

function sumOfArrayWithParameter (array, parameter) {
  let sum = null;
  if (array && array.length > 0 && typeof parameter === 'string') {
    sum = 0;
    for (let e of array) if (e && e.hasOwnProperty(parameter)) sum += e[parameter];
  }
  return sum;
}

To get the sum, simply use it like that:

let sum = sumOfArrayWithParameter(someArray, 'someProperty');
Philippe Genois
  • 599
  • 4
  • 6
0

I was already using jquery. But I think its intuitive enough to just have:

var total_amount = 0; 
$.each(traveler, function( i, v ) { total_amount += v.Amount ; });

This is basically just a short-hand version of @akhouri's answer.

SpiRail
  • 1,377
  • 19
  • 28
0

i honestly got frustrated while reading all the code that where posted as a solution to this cus i'm a new be and i'n trying to add a functionality to a simple app for practice. The simple way to solve this is

let testArray = [5, 7, 8, 4];

function(){
sum = 0;
for(let i = 0; i < testArray.length; i++){
    sum += testArray[i];
}

// will give you the sum of the array

  • Its old but it works guys. Why all the downvotes? I know most modern JS devs would reject this but is there a performance (one that has actually been measured) reason to reject this? I wouldn't use it professionally because of peer pressure from the team and if the team doesn't want it then its fine. But for a personally project, if there is no performance penalty then its fine. Its just code. – Andy May 31 '22 at 11:17
0

You can use jscollection library for database like query job easily in just one line https://github.com/somnathpanja/jscollection

var total = List.extend(traveler).select('Amount').sum();
Somnath
  • 3,247
  • 4
  • 28
  • 42