Лучший способ суммировать значение свойства в массиве

184

У меня есть что-то вроде этого:

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

Ответ

Я решил использовать предложение @ gruff-bunny, чтобы избежать прототипирования нативного объекта (Array)

Я только что сделал небольшую модификацию его ответа, проверяя массив и значение sum не равны null, это моя последняя реализация:

$scope.sum = function (items, prop) {
    if (items == null) {
        return 0;
    }
    return items.reduce(function (a, b) {
        return b[prop] == null ? a : a + b[prop];
    }, 0);
};
nramirez
источник
2
Вы можете опубликовать свой ответ в качестве ответа .
Кен Кин

Ответы:

124

Обновленный ответ

Из-за всех недостатков добавления функции в прототип Array я обновляю этот ответ, чтобы предоставить альтернативу, которая поддерживает синтаксис, аналогичный синтаксису, первоначально запрошенному в вопросе.

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

Оригинальный ответ

Поскольку это массив, вы можете добавить функцию в прототип Array.

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/

bottens
источник
1
спасибо @ sp00m теперь я изменил свою реализацию с array.reduce так же, как ответил gruff-bunny.
Нрамирез
Вам может понадобиться заполнить функцию уменьшения, если вам требуется поддержка старых браузеров: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bottens
Пожалуйста, не расширяйте, так Array.prototypeкак это может нарушить ваш код в будущем. Почему расширение нативных объектов - плохая практика? ,
ул
@bottens, что если в массиве есть нулевой объект?
Обби
228

Я знаю, что на этот вопрос есть приемлемый ответ, но я подумал, что я добавлю альтернативу, которая использует 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');в начале функции контроллера?
Пн
Да, если это происходит после создания функции суммы.
Gruff Bunny
14
Есть ли причина понизить голосование 6 октября 2015 года? Если есть проблема с ответом, пожалуйста, добавьте комментарий, и я исправлю его. Никто не учится на анонимных отрицательных голосах.
Гриф Банни
Это может привести к NaN, если пропа не существует (не определено) ни в одном из объектов в массиве. Я не знаю, является ли это лучшей проверкой, но в моем случае это сработало: function (items, prop) {return items.reduce (function (a, b) {if (! IsNaN (b [prop]))) { return a + b [prop];} else {return a}}, 0); }
claytronicon
131

Просто еще один вариант: для этого и созданы nativeфункции JavaScript (Map и Reduce являются мощными источниками во многих языках).MapReduce

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.
SoEzPz
источник
4
return Number(item.Amount);проверить только номер
diEcho
4
Я бы поспорил только с именем переменной prevв функции Reduce. Для меня это означает, что вы получаете предыдущее значение в массиве. Но на самом деле это сокращение всех предыдущих значений. Мне нравится accumulator, aggregatorили sumSoFarдля большей ясности.
carpiediem
94

Я всегда избегаю изменения метода-прототипа и добавления библиотеки, так что это мое решение:

Достаточно использовать метод-прототип Reduce Array

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);
Эрик Марселино
источник
3
Второй параметр (который определяет начальное значение a) делает трюк при использовании reduceс объектами: в первой итерации aне будет первым объектом itemsмассива, а будет 0.
Парзифал
Как функция: const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0); Использование:sumBy(traveler, 'Amount') // => 235
Рафи
2
Как программист старой школы, reduceсинтаксис выглядит совершенно тупым для меня. Мой мозг до сих пор "родной" понимает это лучше:let total = 0; items.forEach((item) => { total += item.price; });
AndrWeisR
1
@AndrWeisR Мне нравится ваше решение. Кроме того, использование нативного языка - это такое модное слово, которое вряд ли можно объяснить, для чего оно предназначено. Я сделал еще более простую строку кода, основанную на вашем решении, и она отлично работала. let total = 0; items.forEach(item => total += item.price)
Ян Постон Фреймер
22

Я решил отбросить два своих цента: это одна из тех операций, которая всегда должна быть чисто функциональной, не полагаться на какие-либо внешние переменные. Некоторые уже дали хороший ответ, используя reduceэто путь сюда.

Поскольку большинство из нас уже может позволить себе использовать синтаксис ES2015, вот мое предложение:

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

Мы делаем его неизменной функцией, пока мы на ней. Что reduceделает здесь просто: Start со значением 0для аккумулятора, и добавить значение текущего элемента зацикленного на него.

Yay для функционального программирования и ES2015! :)

Рикардо Магальес
источник
22

Альтернатива для улучшения читаемости и использования Mapи 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);

Повторно используемая функция:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);
codingbamby
источник
Идеальный друг "+1"
Маянк Пандейз
Использовать уменьшить значение по умолчанию, что .reduce(*** => *** + ***, 0);другим не хватало, что "+1"
FDisk
19

Вы можете сделать следующее:

$scope.traveler.map(o=>o.Amount).reduce((a,c)=>a+c);
greenif
источник
13

Это работает для меня TypeScriptи 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);

Я надеюсь, что это полезно.

AminRostami
источник
4

Я не уверен, что это было упомянуто еще. Но для этого есть функция lodash . Фрагмент ниже, где значение - это ваш атрибут, а сумма - это «значение».

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

Оба будут работать.

Лиам Миддлтон
источник
2

также можно использовать Array.prototype.forEach ()

let totalAmount = 0;
$scope.traveler.forEach( data => totalAmount = totalAmount + data.Amount);
return totalAmount;
akhouri
источник
1

Вот однострочник, использующий функции стрелок ES6.

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');
Стив Бриз
источник
1

Как суммировать массив объектов с помощью 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))
`

kvimalkr55
источник
1

Из массива объектов

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
источник
0

Я уже использовал jquery. Но я думаю, что это достаточно интуитивно понятно, чтобы просто иметь:

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

По сути, это всего лишь краткая версия ответа @ akhouri.

SpiRail
источник
0

Вот решение, которое я считаю более гибким:

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

Чтобы получить сумму, просто используйте ее так:

let sum = sumOfArrayWithParameter(someArray, 'someProperty');
Филипп Дженуа
источник