Lepszy sposób na sumowanie wartości właściwości w tablicy

184

Mam coś takiego:

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

Teraz, aby uzyskać całkowitą kwotę tej tablicy, robię coś takiego:

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

Jest to łatwe, gdy jest tylko jedna tablica, ale mam inne tablice z inną nazwą właściwości, które chciałbym zsumować.

Byłbym szczęśliwszy, gdybym mógł zrobić coś takiego:

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

Ale nie wiem, jak przez to przejść w taki sposób, żebym mógł go ponownie wykorzystać w przyszłości w następujący sposób:

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

Odpowiedź

Zdecydowałem się skorzystać z sugestii @ gruff-bunny, więc unikam prototypowania obiektu natywnego (Array)

Właśnie dokonałem niewielkiej modyfikacji jego odpowiedzi, sprawdzając poprawność tablicy, a wartość do zsumowania nie jest zerowa, oto moja ostateczna implementacja:

$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
źródło
2
Możesz zamieścić swoją odpowiedź jako odpowiedź .
Ken Kin

Odpowiedzi:

124

Zaktualizowana odpowiedź

Ze względu na wszystkie wady dodania funkcji do prototypu Array, aktualizuję tę odpowiedź, aby zapewnić alternatywę, która zachowuje składnię podobną do składni pierwotnie żądanej w pytaniu.

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

Oryginalna odpowiedź

Ponieważ jest to tablica, możesz dodać funkcję do prototypu 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"))

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

bottens
źródło
1
dzięki @ sp00m teraz zmieniłem implementację z array.reduce, tak jak odpowiedział szorstki-bunny.
nramirez
Może być konieczne wypełnienie funkcji redukcji, jeśli chcesz obsługiwać starsze przeglądarki: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
bottens
Nie przedłużaj, Array.prototypeponieważ może to spowodować uszkodzenie kodu w przyszłości. Dlaczego rozszerzanie obiektów natywnych jest złą praktyką? .
str.
@bottens co się stanie, jeśli w tablicy znajduje się obiekt zerowy?
Obby
228

Wiem, że to pytanie ma akceptowaną odpowiedź, ale pomyślałem, że poddam się alternatywą, która używa array.reduce , widząc, że sumowanie tablicy jest kanonicznym przykładem redukcji:

$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
źródło
genialna odpowiedź, czy da się umieścić $scope.travelerTotal = $scope.sum($scope.traveler, 'Amount');na początku funkcję kontrolera?
pon.
Tak, jeśli nastąpi to po utworzeniu funkcji sumy.
Gruff Bunny
14
Czy jest jakiś powód do głosowania przeciw 6 października 2015 r.? Jeśli jest problem z odpowiedzią, dodaj komentarz, a ja go poprawię. Nikt nie uczy się z anonimowych głosów przeciw.
Gruff Bunny
Może to skutkować NaN, jeśli właściwość nie istnieje (niezdefiniowana) w jednym z obiektów w tablicy. Nie wiem, czy to najlepszy sposób sprawdzenia, ale zadziałało w moim przypadku: function (items, prop) {return items.reduce (function (a, b) {if (! IsNaN (b [prop]))) { return a + b [prop];} else {return a}}, 0); }
claytronicon
129

Tylko inny weźmie, to co nativefunkcje JavaScript Mapi Reducezostały zbudowane dla (Map i Reduce są potęg w wielu językach).

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

Uwaga : tworząc oddzielne mniejsze funkcje uzyskujemy możliwość ich ponownego wykorzystania.

// 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
źródło
4
return Number(item.Amount);sprawdzić tylko numer
diEcho
4
Spierałbym się tylko z nazwą zmiennej prevw funkcji redukuj. Dla mnie oznacza to, że otrzymujesz poprzednią wartość w tablicy. Ale tak naprawdę jest to redukcja wszystkich poprzednich wartości. Lubię accumulator, aggregatorlub sumSoFardla jasności.
carpiediem
93

Zawsze unikam zmiany metody prototypu i dodawania biblioteki, więc takie jest moje rozwiązanie:

Wystarczy użyć metody prototypowej redukcji Array

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);
Eric Marcelino
źródło
3
Drugi parametr (który określa początkową wartość a) załatwia sprawę, gdy jest używany reducez obiektami: w pierwszej iteracji anie będzie pierwszym obiektem itemstablicy, zamiast tego będzie 0.
Parziphal
Jako funkcja: const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0); Zastosowanie:sumBy(traveler, 'Amount') // => 235
Rafi
2
Jako programista ze starej szkoły reduceskładnia wydaje mi się całkowicie tępa. Mój mózg nadal „natywnie” to rozumie lepiej:let total = 0; items.forEach((item) => { total += item.price; });
AndrWeisR
1
@AndrWeisR Twoje rozwiązanie podoba mi się najbardziej. Również użycie natywnego, ponieważ jest to takie modne słowo, które prawie nie wyjaśnia, do czego służy. Stworzyłem jeszcze bardziej podstawową linię kodu w oparciu o twoje rozwiązanie i działało świetnie. let total = 0; items.forEach(item => total += item.price)
Ian Poston Framer
22

Pomyślałem, że zrzucę na to moje dwa grosze: jest to jedna z tych operacji, które zawsze powinny być czysto funkcjonalne, nie polegające na żadnych zewnętrznych zmiennych. Kilku dało już dobrą odpowiedź, używając reducejest sposobem, aby przejść tutaj.

Ponieważ większość z nas może już sobie pozwolić na używanie składni ES2015, oto moja propozycja:

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

Robimy z tego niezmienną funkcję, gdy jesteśmy na tym. To, reduceco tutaj robi, jest po prostu następujące: Rozpocznij od wartości 0dla akumulatora i dodaj do niego wartość bieżącej zapętlonej pozycji.

Yay za programowanie funkcjonalne i ES2015! :)

Ricardo Magalhães
źródło
22

Alternatywa dla lepszej czytelności i używania Maporaz 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);

Funkcja wielokrotnego użytku:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);
codingbamby
źródło
Idealny kumpel, „+1”
Mayank Pandeyz
Użyj zmniejsz domyślną wartość, .reduce(*** => *** + ***, 0);której brakowało innym, że „+1”
FDisk
19

Możesz wykonać następujące czynności:

$scope.traveler.map(o=>o.Amount).reduce((a,c)=>a+c);
greenif
źródło
13

Działa na mnie TypeScript i 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);

Mam nadzieję, że jest przydatny.

AminRostami
źródło
4

Nie jestem pewien, czy zostało to jeszcze wspomniane. Ale jest do tego funkcja lodash . Fragment poniżej, gdzie wartość jest atrybutem do zsumowania, to „wartość”.

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

Oba będą działać.

Liam Middleton
źródło
2

można również użyć Array.prototype.forEach ()

let totalAmount = 0;
$scope.traveler.forEach( data => totalAmount = totalAmount + data.Amount);
return totalAmount;
akhouri
źródło
1

Oto jednowierszowy wykorzystujący funkcje strzałek 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
źródło
1

Jak podsumować tablicę obiektów za pomocą 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
źródło
1

Z tablicy obiektów

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
źródło
0

Używałem już jquery. Ale myślę, że jest na tyle intuicyjny, że wystarczy:

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

To jest po prostu krótka wersja odpowiedzi @ akhouri.

SpiRail
źródło
0

Oto rozwiązanie, które uważam za bardziej elastyczne:

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

Aby otrzymać sumę, użyj jej w ten sposób:

let sum = sumOfArrayWithParameter(someArray, 'someProperty');
Philippe Genois
źródło