Cách tốt hơn để tổng hợp một giá trị thuộc tính trong một mảng


182

Tôi có một cái gì đó như thế này:

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

Bây giờ để có tổng số tiền của mảng này, tôi đang làm một cái gì đó như thế này:

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

Thật dễ dàng khi chỉ có một mảng, nhưng tôi có các mảng khác với một tên thuộc tính khác mà tôi muốn tính tổng.

Tôi sẽ hạnh phúc hơn nếu tôi có thể làm một cái gì đó như thế này:

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

Nhưng tôi không biết làm thế nào để vượt qua điều này theo cách mà tôi có thể tái sử dụng nó trong tương lai như thế này:

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

Câu trả lời

Tôi đã quyết định sử dụng đề xuất @ gruff-bunny, vì vậy tôi tránh tạo mẫu đối tượng gốc (Array)

Tôi vừa thực hiện một sửa đổi nhỏ cho câu trả lời của anh ấy để xác thực mảng và giá trị tổng không phải là null, đây là lần thực hiện cuối cùng của tôi:

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

2
Bạn có thể gửi câu trả lời của bạn như một câu trả lời .
Ken Kin

Câu trả lời:


124

Cập nhật câu trả lời

Do tất cả các nhược điểm của việc thêm một hàm vào nguyên mẫu Array, tôi đang cập nhật câu trả lời này để cung cấp một thay thế giữ cho cú pháp tương tự như cú pháp được yêu cầu ban đầu trong câu hỏi.

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

Câu trả lời gốc

Vì nó là một mảng nên bạn có thể thêm một hàm vào nguyên mẫu 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"))

Câu đố: http://jsfiddle.net/9BAmj/


1
cảm ơn @ sp00m bây giờ tôi đã thay đổi cách thực hiện của mình với mảng.reduce giống như câu trả lời cộc cằn.
nramirez

Bạn có thể cần phải polyfill sự giảm chức năng nếu bạn cần hỗ trợ các trình duyệt cũ: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
bottens

Vui lòng không gia hạn Array.prototypevì nó có thể phá vỡ mã của bạn trong tương lai. Tại sao mở rộng các đối tượng bản địa là một thực hành xấu? .
str

@bottens nếu có một đối tượng null trong mảng thì sao?
Obby

228

Tôi biết rằng câu hỏi này có câu trả lời được chấp nhận nhưng tôi nghĩ rằng tôi sẽ đưa ra một giải pháp thay thế sử dụng mảng.reduce , khi thấy rằng việc tóm tắt một mảng là ví dụ điển hình để giảm:

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

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

Fiddle


Câu trả lời tuyệt vời, có thể đặt $scope.travelerTotal = $scope.sum($scope.traveler, 'Amount');ở đầu chức năng điều khiển?

Có nếu nó xuất hiện sau khi hàm sum được tạo.
Bunny cộc cằn

14
Bất kỳ lý do cho các downvote vào ngày 6 tháng 10 năm 2015? Nếu có vấn đề với câu trả lời thì xin vui lòng thêm một nhận xét và tôi sẽ sửa nó. Không ai học được từ downvote ẩn danh.
Bunny cộc cằn

Điều này có thể dẫn đến NaN, nếu prop không tồn tại (không xác định) trong một trong các đối tượng trong mảng. Tôi không biết đây có phải là kiểm tra tốt nhất không, nhưng nó hoạt động cho trường hợp của tôi: function (items, prop) {return items.reduce (function (a, b) {if (! IsNaN (b [prop])) { return a + b [prop];} other {return a}}, 0); }
claytronicon

129

Chỉ cần thực hiện một lần nữa, đây là những nativechức năng JavaScript MapReduceđược xây dựng cho (Bản đồ và Thu nhỏ là các cường quốc trong nhiều ngôn ngữ).

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

Lưu ý : bằng cách thực hiện các chức năng nhỏ hơn riêng biệt, chúng tôi có khả năng sử dụng lại chúng.

// 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.

4
return Number(item.Amount);để chỉ kiểm tra số
diEcho

4
Tôi chỉ tranh luận với tên của biến prevtrong hàm giảm. Đối với tôi, điều đó ngụ ý rằng bạn đang nhận được giá trị trước đó trong mảng. Nhưng thực sự đó là sự giảm tất cả các giá trị trước đó. Tôi thích accumulator, aggregatorhoặc sumSoFarcho rõ ràng.
Carpiediem

92

Tôi luôn tránh thay đổi phương thức nguyên mẫu và thêm thư viện vì vậy đây là giải pháp của tôi:

Sử dụng phương thức nguyên mẫu giảm Array là đủ

// + operator for casting to Number
items.reduce((a, b) => +a + +b.price, 0);

3
Tham số thứ hai (xác định giá trị ban đầu của a) thực hiện mẹo khi sử dụng reducevới các đối tượng: trong lần lặp đầu tiên, asẽ không phải là đối tượng đầu tiên của itemsmảng, thay vào đó sẽ là 0.
Parziphal

Là một chức năng: const sumBy = (items, prop) => items.reduce((a, b) => +a + +b[prop], 0); Cách sử dụng:sumBy(traveler, 'Amount') // => 235
Rafi

2
Là một lập trình viên cũ, reducecú pháp trông hoàn toàn khó hiểu với tôi. Bộ não của tôi vẫn "tự nhiên" hiểu điều này tốt hơn:let total = 0; items.forEach((item) => { total += item.price; });
AndrweisR

1
@AndrweisR Tôi thích giải pháp của bạn nhất. Ngoài ra, việc sử dụng từ gốc như một từ buzz mà hầu như không được giải thích ý nghĩa của nó. Tôi đã tạo ra một dòng mã cơ bản hơn nữa dựa trên giải pháp của bạn và nó hoạt động rất tốt. let total = 0; items.forEach(item => total += item.price)
Ian Poston Framer

22

Tôi nghĩ rằng tôi đã bỏ hai xu của mình vào việc này: đây là một trong những hoạt động phải luôn hoàn toàn hoạt động, không dựa vào bất kỳ biến số bên ngoài nào. Một số đã đưa ra một câu trả lời tốt, sử dụng reducelà cách để đi đến đây.

Vì hầu hết chúng ta đều có thể đủ khả năng sử dụng cú pháp ES2015, đây là đề xuất của tôi:

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

Chúng tôi đang làm cho nó một chức năng bất biến trong khi chúng tôi đang ở đó. Những gì reduceđang làm ở đây chỉ đơn giản là thế này: Bắt đầu với giá trị của 0bộ tích lũy và thêm giá trị của mục được lặp hiện tại vào nó.

Yay cho lập trình chức năng và ES2015! :)


21

Thay thế để cải thiện khả năng đọc và sử dụng MapReduce:

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

Chức năng sử dụng lại:

const calculateSum = (obj, field) => obj
  .map(items => items.attributes[field])
  .reduce((prev, curr) => prev + curr, 0);

Người bạn đời hoàn hảo, "+1"
Mayank Pandeyz

Sử dụng giảm giá trị mặc định mà .reduce(*** => *** + ***, 0);những người khác đã thiếu là "+1"
FDisk

19

Bạn có thể làm như sau:

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

13

Nó làm việc cho tôi trong TypeScriptJavaScript:

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

Tôi hy vọng là hữu ích.


4

Tôi không chắc điều này đã được đề cập. Nhưng có một chức năng lodash cho điều đó. Đoạn trích bên dưới trong đó giá trị là thuộc tính của bạn để tính tổng là 'giá trị'.

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

Cả hai sẽ làm việc.



1

Đây là một lớp lót sử dụng các chức năng mũi tên 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

Cách tính tổng mảng của đối tượng bằng 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))
`


1

Từ mảng đối tượng

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

0

Tôi đã sử dụng jquery. Nhưng tôi nghĩ nó đủ trực quan để có:

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

Đây về cơ bản chỉ là một phiên bản ngắn của câu trả lời của @ akhouri.


0

Đây là một giải pháp tôi thấy linh hoạt hơn:

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

Để có được tổng, chỉ cần sử dụng nó như thế:

let sum = sumOfArrayWithParameter(someArray, 'someProperty');
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.