Melhor maneira de somar um valor de propriedade em uma matriz

182

Eu tenho algo parecido com isto:

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

Agora, para ter uma quantidade total dessa matriz, estou fazendo algo assim:

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

É fácil quando existe apenas uma matriz, mas tenho outras matrizes com um nome de propriedade diferente que gostaria de somar.

Eu ficaria mais feliz se eu pudesse fazer algo assim:

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

Mas não sei como passar por isso de uma maneira que possa reutilizá-lo no futuro assim:

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

Responda

Decidi usar a sugestão @ gruff-bunny, para evitar a criação de protótipos de objetos nativos (Array)

Eu apenas fiz uma pequena modificação na resposta dele validando a matriz e o valor a somar não é nulo, esta é minha implementação 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
fonte
2
Você pode postar sua resposta como resposta .
Ken Kin

Respostas:

124

Resposta atualizada

Devido a todas as desvantagens de adicionar uma função ao protótipo Array, estou atualizando esta resposta para fornecer uma alternativa que mantenha a sintaxe semelhante à sintaxe originalmente solicitada na pergunta.

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

Resposta original

Como é uma matriz, você pode adicionar uma função ao protótipo da 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"))

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

bottens
fonte
1
thanks @ sp00m agora eu mudei minha implementação com array.reduce, exatamente como o gruff-bunny respondeu.
Nramirez
Você pode precisar de polyfill a função de reduzir, se você precisa para apoiar os navegadores mais antigos: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Bottens
Não estenda, Array.prototypepois isso pode quebrar seu código no futuro. Por que estender objetos nativos é uma má prática? .
str
@bottens e se houver um objeto nulo na matriz?
Obby 11/07
228

Eu sei que esta pergunta tem uma resposta aceita, mas pensei em usar uma alternativa que usa array.reduce , visto que somar uma matriz é o exemplo canônico de reduzir:

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

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

Fiddle

Coelho áspero
fonte
resposta brilhante, é possível colocar $scope.travelerTotal = $scope.sum($scope.traveler, 'Amount');no início da função do controlador?
Mo.
Sim se vier depois que a função soma for criada.
Gruff Bunny
14
Algum motivo para o voto negativo em 6 de outubro de 2015? Se houver um problema com a resposta, adicione um comentário e eu o corrigirei. Ninguém aprende com votos negativos anônimos.
Gruff Bunny
Isso pode resultar em NaN, se o suporte não existir (indefinido) em um dos objetos na matriz. Não sei se essa é a melhor verificação, mas funcionou para o meu caso: function (items, prop) {retornar items.reduce (function (a, b) {if (! IsNaN (b [prop])) { retornar a + b [prop];} else {retornar a}}, 0); }
claytronicon
129

Apenas mais um exemplo, é para isso que o nativeJavaScript funciona Mape Reducefoi criado (o Map and Reduce são potências em vários 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 : criando funções menores separadas, temos a capacidade de usá-las novamente.

// 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
fonte
4
return Number(item.Amount);para verificar se há número só
diEcho
4
Eu argumentaria apenas com o nome da variável prevna função de redução. Para mim, isso implica que você está obtendo o valor anterior na matriz. Mas na verdade é a redução de todos os valores anteriores. Eu gosto accumulator, aggregatorou sumSoFarpara maior clareza.
precisa saber é
92

Eu sempre evito alterar o método do protótipo e adicionar a biblioteca, então esta é a minha solução:

O uso do método de protótipo de redução de matriz é suficiente

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);
Eric Marcelino
fonte
3
O segundo parâmetro (que determina o valor inicial de a) faz o truque ao usar reduceobjetos: na primeira iteração, anão será o primeiro objeto da itemsmatriz, mas sim 0.
Parziphal
Como uma função: const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0); Uso:sumBy(traveler, 'Amount') // => 235
Rafi 29/11
2
Como programador da velha escola, a reducesintaxe parece totalmente obtusa para mim. Meu cérebro ainda "nativamente" entende isso melhor:let total = 0; items.forEach((item) => { total += item.price; });
AndrWeisR
1
@AndrWeisR Gosto mais da sua solução. Também o uso de nativo, pois é uma palavra tão popular que dificilmente é explicada para que serve. Fiz uma linha de código ainda mais básica com base na sua solução e funcionou muito bem. let total = 0; items.forEach(item => total += item.price)
Ian Poston Framer,
22

Eu pensei em colocar meus dois centavos nisso: essa é uma daquelas operações que sempre devem ser puramente funcionais, sem depender de variáveis ​​externas. Alguns já deram uma boa resposta, usandoreduce é o caminho a percorrer aqui.

Como a maioria de nós já pode se dar ao luxo de usar a sintaxe do ES2015, aqui está minha proposta:

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

Estamos criando uma função imutável enquanto estamos nisso. O que reduceestá fazendo aqui é simplesmente o seguinte: Comece com um valor de 0para o acumulador e adicione o valor do item em loop atual a ele.

Yay para programação funcional e ES2015! :)

Ricardo Magalhães
fonte
21

Alternativa para melhorar a legibilidade e usar Mape 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);

Função reutilizável:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);
codingbamby
fonte
Companheiro perfeito, "+1"
Mayank Pandeyz em 26/02
Use reduzir o valor padrão .reduce(*** => *** + ***, 0);que faltava aos outros que "+1"
FDisk 26/03
19

Você pode fazer o seguinte:

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

Está trabalhando para mim TypeScripte 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 que seja útil.

AminRostami
fonte
4

Não sei se isso foi mencionado ainda. Mas há uma função lodash para isso. O snippet abaixo de onde value é seu atributo, a soma é 'value'.

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

Ambos irão funcionar.

Liam Middleton
fonte
2

também pode usar Array.prototype.forEach ()

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

Aqui está uma linha única usando as funções de seta 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
fonte
1

Como somar matriz de objeto 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
fonte
1

Da 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
fonte
0

Eu já estava usando jquery. Mas acho que é intuitivo o suficiente para ter:

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

Esta é basicamente apenas uma versão abreviada da resposta de @ akhouri.

SpiRail
fonte
0

Aqui está uma solução que acho mais flexível:

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 obter a soma, basta usá-lo assim:

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