Mejor manera de sumar un valor de propiedad en una matriz

184

Tengo algo como esto:

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

Ahora para tener una cantidad total de esta matriz, estoy haciendo algo como esto:

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

Es fácil cuando solo hay una matriz, pero tengo otras matrices con un nombre de propiedad diferente que me gustaría sumar.

Sería más feliz si pudiera hacer algo como esto:

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

Pero no sé cómo pasar por esto de una manera que pueda reutilizarlo en el futuro de esta manera:

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

Responder

Decidí usar la sugerencia de @ gruff-bunny, así que evito crear prototipos de objetos nativos (Array)

Acabo de hacer una pequeña modificación a su respuesta validando la matriz y el valor para sumar no es nulo, esta es mi implementación final:

$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
fuente
2
Puede publicar su respuesta como respuesta .
Ken Kin

Respuestas:

124

Respuesta actualizada

Debido a todas las desventajas de agregar una función al prototipo de matriz, estoy actualizando esta respuesta para proporcionar una alternativa que mantenga la sintaxis similar a la sintaxis solicitada originalmente en la pregunta.

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

Respuesta original

Como es una matriz, puede agregar una función al prototipo de matriz.

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

El violín: http://jsfiddle.net/9BAmj/

bottens
fuente
1
gracias @ sp00m ahora he cambiado mi implementación con array.reduce tal como respondió gruff-bunny
nramirez
Es posible que deba rellenar la función de reducción si necesita admitir navegadores antiguos: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bottens el
No extienda, Array.prototypeya que podría romper su código en el futuro. ¿Por qué extender objetos nativos es una mala práctica? .
str
@bottens, ¿qué pasa si hay un objeto nulo en la matriz?
Obby
228

Sé que esta pregunta tiene una respuesta aceptada, pero pensé en incorporar una alternativa que usa array.reduce , ya que sumar una matriz es el ejemplo canónico para reducir:

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

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

Fiddle

Conejito Gruff
fuente
respuesta brillante, ¿es posible poner $scope.travelerTotal = $scope.sum($scope.traveler, 'Amount');al principio de la función del controlador?
Lun.
Sí, si viene después de crear la función de suma.
Gruff Bunny
14
¿Alguna razón para el voto negativo el 6 de octubre de 2015? Si hay un problema con la respuesta, agregue un comentario y lo corregiré. Nadie aprende de votos negativos anónimos.
Gruff Bunny
Esto puede resultar en NaN, si el accesorio no existe (indefinido) en uno de los objetos en la matriz. No sé si esta es la mejor verificación, pero funcionó para mi caso: function (items, prop) {return items.reduce (function (a, b) {if (! IsNaN (b [prop]))) { devuelve a + b [prop];} else {return a}}, 0); }
claytronicon
131

Solo otra toma, esto es para lo que nativefunciona JavaScript Mapy para lo que Reducefueron construidas (Map and Reduce son potencias en muchos idiomas).

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

Nota : al hacer funciones más pequeñas por separado, tenemos la capacidad de usarlas nuevamente.

// 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
fuente
44
return Number(item.Amount);para verificar solo el número
diEcho
44
Solo discutiría con el nombre de la variable preven la función reducir. Para mí, eso implica que está obteniendo el valor anterior en la matriz. Pero realmente es la reducción de todos los valores anteriores. Me gusta accumulator, aggregatoro sumSoFarpor claridad.
carpiediem
93

Siempre evito cambiar el método del prototipo y agregar la biblioteca, así que esta es mi solución:

Usar el método de prototipo de reducción de matriz es suficiente

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);
Eric Marcelino
fuente
3
El segundo parámetro (que determina el valor inicial de a) hace el truco cuando se usa reducecon objetos: en la primera iteración, ano será el primer objeto de la itemsmatriz, sino que lo será 0.
Parziphal
Como una función: const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0); Uso:sumBy(traveler, 'Amount') // => 235
Rafi
2
Como programador de la vieja escuela, la reducesintaxis me parece totalmente obtusa. Mi cerebro todavía "nativamente" entiende esto mejor:let total = 0; items.forEach((item) => { total += item.price; });
AndrWeisR
1
@AndrWeisR Me gusta más tu solución. También el uso de nativo, ya que es una palabra de moda que apenas se explica para qué sirve. Hice una línea de código aún más básica basada en su solución y funcionó muy bien. let total = 0; items.forEach(item => total += item.price)
Ian Poston Framer
22

Pensé en dejar mis dos centavos en esto: esta es una de esas operaciones que siempre debe ser puramente funcional, no depender de ninguna variable externa. Algunos ya dieron una buena respuesta, usandoreduce es el camino a seguir aquí.

Como la mayoría de nosotros ya podemos permitirnos usar la sintaxis ES2015, esta es mi propuesta:

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

Lo estamos haciendo una función inmutable mientras estamos en ello. Quéreduce está haciendo aquí es simplemente esto: comience con un valor de 0para el acumulador y agregue el valor del elemento en bucle actual.

¡Yay para programación funcional y ES2015! :)

Ricardo Magalhães
fuente
22

Alternativa para mejorar la legibilidad y el uso Mapy 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);

Función reutilizable:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);
codingbamby
fuente
Compañero perfecto, "+1"
Mayank Pandeyz
Use reducir el valor predeterminado a .reduce(*** => *** + ***, 0);otros le faltaba ese "+1"
FDisk
19

Puedes hacer lo siguiente:

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

Me está funcionando en TypeScripty 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);

Espero sea de utilidad.

AminRostami
fuente
4

No estoy seguro de que esto haya sido mencionado todavía. Pero hay una función lodash para eso. El fragmento debajo de donde el valor es su atributo para sumar es 'valor'.

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

Ambos funcionarán.

Liam Middleton
fuente
2

también puede usar Array.prototype.forEach ()

let totalAmount = 0;
$scope.traveler.forEach( data => totalAmount = totalAmount + data.Amount);
return totalAmount;
akhouri
fuente
1

Aquí hay una línea usando las funciones de flecha 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');
Steve Breese
fuente
1

Cómo sumar una matriz de objetos usando 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
fuente
1

De una matriz de objetos

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
fuente
0

Ya estaba usando jquery. Pero creo que es lo suficientemente intuitivo como para tener:

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

Esto es básicamente una versión abreviada de la respuesta de @ akhuri.

SpiRail
fuente
0

Aquí hay una solución que encuentro más 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;
}

Para obtener la suma, simplemente úsela así:

let sum = sumOfArrayWithParameter(someArray, 'someProperty');
Philippe Genois
fuente