Javascript giảm trên mảng các đối tượng


225

Nói rằng tôi muốn tổng hợp a.xcho từng yếu tố trong arr.

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a.x + b.x})
>> NaN

Tôi có lý do để tin rằng rìu không được xác định tại một số điểm.

Sau đây hoạt động tốt

arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7

Tôi đang làm gì sai trong ví dụ đầu tiên?


1
Ngoài ra, tôi nghĩ bạn có nghĩa là arr.reduce(function(a,b){return a + b})trong ví dụ thứ hai.
Jamie Wong

1
Cảm ơn vì sự đúng đắn của bạn. Tôi đã tìm thấy giảm ở đây: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/ chủ
YXD

6
@Jamie Wong nó thực sự là một phần của JavaScript 1.8
JaredMcAteer

@OrigenSyn yeah - chỉ cần nhìn thấy điều đó. Thú vị, nhưng vì nó không có hỗ trợ bản địa đầy đủ, nên việc triển khai vẫn là vấn đề khi trả lời các câu hỏi như thế này.
Jamie Wong

3
Các phiên bản JavaScript chỉ là phiên bản của trình thông dịch Firefox, thật khó hiểu khi tham chiếu chúng. Chỉ có ES3 và ES5.
Raynos

Câu trả lời:


279

Sau lần lặp đầu tiên, bạn sẽ trả về một số và sau đó cố gắng lấy xthuộc tính của nó để thêm vào đối tượng tiếp theo undefinedvà toán học liên quan đến undefinedkết quả NaN.

hãy thử trả về một đối tượng chứa một thuộc xtính với tổng các thuộc tính x của các tham số:

var arr = [{x:1},{x:2},{x:4}];

arr.reduce(function (a, b) {
  return {x: a.x + b.x}; // returns object with property x
})

// ES6
arr.reduce((a, b) => ({x: a.x + b.x}));

// -> {x: 7}

Giải thích thêm từ ý kiến:

Giá trị trả về của mỗi lần lặp [].reduceđược sử dụng làm abiến trong lần lặp tiếp theo.

Lặp 1: a = {x:1}, b = {x:2}, {x: 3}giao cho atrong lặp 2

Lặp lại 2: a = {x:3}, b = {x:4}.

Vấn đề với ví dụ của bạn là bạn đang trả về một số bằng chữ.

function (a, b) {
  return a.x + b.x; // returns number literal
}

Lặp 1: a = {x:1}, b = {x:2}, // returns 3như atrong phiên bản kế tiếp

Lặp lại 2 : a = 3, b = {x:2}trả vềNaN

Một literal số 3không (thường) có một tài sản gọi là xvì vậy nó undefinedundefined + b.xlợi nhuận NaNNaN + <anything>luôn luôn làNaN

Làm rõ : Tôi thích phương pháp của tôi hơn câu trả lời hàng đầu khác trong chuỗi này vì tôi không đồng ý với ý kiến ​​rằng việc truyền tham số tùy chọn để giảm với số ma thuật để lấy ra một số nguyên thủy là sạch hơn. Nó có thể dẫn đến ít dòng viết hơn nhưng imo nó ít đọc hơn.


7
Chắc chắn giảm có một chức năng để thực hiện các hoạt động trên từng thành phần trong một mảng. Mỗi lần nó trả về một giá trị được sử dụng làm biến số 'a' tiếp theo trong hoạt động. Vì vậy, lần lặp đầu tiên a = {x: 1}, b = {x: 2} sau đó lần lặp thứ hai a = {x: 3} (giá trị kết hợp của lần lặp đầu tiên), b = {x: 4}. Vấn đề với ví dụ của bạn trong lần lặp thứ hai, nó đã cố thêm 3.x + bx, 3 không có thuộc tính được gọi là x nên nó trả về không xác định và thêm nó vào bx (4) trả về Không phải là Số
JaredMcAteer

Tôi hiểu lời giải thích, nhưng tôi vẫn không thấy cách {x: ...} ngăn lặp 2 khi gọi ax? Cái x đó biểu thị điều gì? Tôi đã thử sử dụng phương pháp này với một phương pháp và dường như không hiệu quả
mck

Trên thực tế, chỉ cần đọc stackoverflow.com/questions/2118123/ và hiểu cách thức hoạt động của nó .. nhưng bạn sẽ làm thế nào khi bạn có một phương thức, thay vì chỉ các thuộc tính thẳng?
mck

278

Một cách sạch hơn để thực hiện điều này là cung cấp một giá trị ban đầu:

var arr = [{x:1}, {x:2}, {x:4}];
arr.reduce(function (acc, obj) { return acc + obj.x; }, 0); // 7
console.log(arr);

Lần đầu tiên hàm ẩn danh được gọi, nó được gọi (0, {x: 1})và trả về 0 + 1 = 1. Lần sau, nó được gọi (1, {x: 2})và trả lại 1 + 2 = 3. Sau đó, nó được gọi với (3, {x: 4}), cuối cùng trở lại 7.


1
Đây là giải pháp nếu bạn có giá trị ban đầu (tham số thứ 2 giảm)
TJ.

Thanx, tôi đang tìm gợi ý với đối số thứ hai cho reduce.
nilsole

Đây là ít mã hơn và trực tiếp hơn câu trả lời được chấp nhận - có cách nào để câu trả lời được chấp nhận tốt hơn câu trả lời này không?
jbyrd

1
Điều này giúp tôi xử lý trường hợp khi độ dài mảng chỉ là 1 đôi khi.
HussienK

1
es6 cách var Array = [{x: 1}, {x: 2}, {x: 4}]; let a = Array.reduce ((acc, obj) => acc + obj.x, 0); console.log (a);
amar ghodke

76

TL; DR, đặt giá trị ban đầu

Sử dụng phá hủy

arr.reduce( ( sum, { x } ) => sum + x , 0)

Không phá hủy

arr.reduce( ( sum , cur ) => sum + cur.x , 0)

Với bản đánh máy

arr.reduce( ( sum, { x } : { x: number } ) => sum + x , 0)

Hãy thử phương pháp phá hủy:

const arr = [ { x: 1 }, { x: 2 }, { x: 4 } ]
const result = arr.reduce( ( sum, { x } ) => sum + x , 0)
console.log( result ) // 7

Chìa khóa cho việc này là thiết lập giá trị ban đầu. Giá trị trả về trở thành tham số đầu tiên của lần lặp tiếp theo.

Kỹ thuật được sử dụng trong câu trả lời hàng đầu không phải là thành ngữ

Câu trả lời được chấp nhận đề xuất KHÔNG vượt qua giá trị "tùy chọn". Điều này là sai, vì cách thành ngữ là tham số thứ hai luôn được đưa vào. Tại sao? Ba lý do:

1. Nguy hiểm - Không vượt qua giá trị ban đầu là nguy hiểm và có thể tạo ra tác dụng phụ và đột biến nếu chức năng gọi lại không cẩn thận.

Hãy chứng kiến

const badCallback = (a,i) => Object.assign(a,i) 

const foo = [ { a: 1 }, { b: 2 }, { c: 3 } ]
const bar = foo.reduce( badCallback )  // bad use of Object.assign
// Look, we've tampered with the original array
foo //  [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]

Tuy nhiên, nếu chúng tôi đã làm theo cách này, với giá trị ban đầu:

const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // { a: 1, b: 2, c: 3 }

Đối với bản ghi, trừ khi bạn có ý định thay đổi đối tượng ban đầu, hãy đặt tham số đầu tiên của Object.assignmột đối tượng trống. Như thế này : Object.assign({}, a, b, c).

2 - Suy luận loại tốt hơn - Khi sử dụng một công cụ như Typecript hoặc trình soạn thảo như VS Code, bạn sẽ nhận được lợi ích khi nói với trình biên dịch ban đầu và nó có thể bắt lỗi nếu bạn làm sai. Nếu bạn không đặt giá trị ban đầu, trong nhiều trường hợp, nó có thể không đoán được và bạn có thể gặp phải lỗi thời gian chạy đáng sợ.

3 - Tôn trọng Functor - JavaScript tỏa sáng nhất khi chức năng bên trong của nó được giải phóng. Trong thế giới chức năng, có một tiêu chuẩn về cách bạn "gấp" hoặc reducemột mảng. Khi bạn gấp hoặc áp dụng một catamorphism để mảng, bạn mất các giá trị của mảng đó để xây dựng một kiểu mới. Bạn cần giao tiếp với loại kết quả - bạn nên làm điều này ngay cả khi loại cuối cùng là của các giá trị trong mảng, mảng khác hoặc bất kỳ loại nào khác.

Hãy nghĩ về nó theo một cách khác. Trong JavaScript, các hàm có thể được chuyển xung quanh như dữ liệu, đây là cách gọi lại hoạt động, kết quả của mã sau đây là gì?

[1,2,3].reduce(callback)

Nó sẽ trả về một số? Một đối tượng? Điều này làm cho nó rõ ràng hơn

[1,2,3].reduce(callback,0)

Đọc thêm về thông số kỹ thuật lập trình tại đây: https://github.com/fantasyland/fantasy-land# Foldable

Một số nền tảng

Các reducephương pháp có hai tham số,

Array.prototype.reduce( callback, initialItem )

Các callbackchức năng có các thông số sau

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }

Đối với lần lặp đầu tiên,

  • Nếu initialItemđược cung cấp, reducehàm sẽ chuyển initialItemnhư là accumulatorvà mục đầu tiên của mảng là itemInArray.

  • Nếu initialItemkhông được cung cấp, các reducechức năng vượt qua mục đầu tiên trong mảng như initialItemvà mục thứ hai trong mảng như itemInArraycó thể gây nhầm lẫn hành vi.

Tôi dạy và đề nghị luôn luôn thiết lập giá trị ban đầu của giảm.

Bạn có thể kiểm tra tài liệu tại:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

Hi vọng điêu nay co ich!


1
Phá hủy chỉ là những gì tôi muốn. Hoạt động như một lá bùa. Cảm ơn bạn!
AlexanderrH

24

Những người khác đã trả lời câu hỏi này, nhưng tôi nghĩ tôi đã đưa ra một cách tiếp cận khác. Thay vì đi trực tiếp đến phép tính tổng rìu, bạn có thể kết hợp bản đồ (từ ax đến x) và thu nhỏ (để thêm x):

arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
   .reduce(function(a,b) {return a + b;});

Phải thừa nhận rằng, nó có thể sẽ chậm hơn một chút, nhưng tôi nghĩ rằng đáng để đề cập đến nó như là một lựa chọn.


8

Để chính thức hóa những gì đã được chỉ ra, một bộ giảm là một biến dị trong đó có hai đối số có thể cùng loại bởi sự trùng hợp và trả về một loại khớp với đối số đầu tiên.

function reducer (accumulator: X, currentValue: Y): X { }

Điều đó có nghĩa là phần thân của bộ giảm tốc cần phải chuyển đổi currentValuevà giá trị hiện tại của accumulatorgiá trị thành giá trị mới accumulator.

Điều này hoạt động theo cách đơn giản, khi thêm vào, bởi vì cả bộ tích lũy và các giá trị phần tử đều cùng loại (nhưng phục vụ các mục đích khác nhau).

[1, 2, 3].reduce((x, y) => x + y);

Điều này chỉ hoạt động bởi vì tất cả chúng là số.

[{ age: 5 }, { age: 2 }, { age: 8 }]
  .reduce((total, thing) => total + thing.age, 0);

Bây giờ chúng tôi đang đưa ra một giá trị bắt đầu cho tập hợp. Giá trị bắt đầu phải là loại mà bạn mong muốn bộ tổng hợp trở thành (loại bạn mong đợi sẽ xuất hiện dưới dạng giá trị cuối cùng), trong phần lớn các trường hợp. Mặc dù bạn không bị buộc phải làm điều này (và không nên), điều quan trọng là phải ghi nhớ.

Khi bạn biết điều đó, bạn có thể viết các mức giảm có ý nghĩa cho các vấn đề về mối quan hệ n: 1 khác.

Loại bỏ các từ lặp đi lặp lại:

const skipIfAlreadyFound = (words, word) => words.includes(word)
    ? words
    : words.concat(word);

const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);

Cung cấp một số lượng tất cả các từ được tìm thấy:

const incrementWordCount = (counts, word) => {
  counts[word] = (counts[word] || 0) + 1;
  return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });

Giảm một mảng các mảng, thành một mảng phẳng duy nhất:

const concat = (a, b) => a.concat(b);

const numbers = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
].reduce(concat, []);

Bất cứ khi nào bạn đang muốn đi từ một loạt các thứ, đến một giá trị không khớp với tỷ lệ 1: 1, giảm là điều bạn có thể cân nhắc.

Trên thực tế, cả bản đồ và bộ lọc đều có thể được thực hiện dưới dạng giảm:

const map = (transform, array) =>
  array.reduce((list, el) => list.concat(transform(el)), []);

const filter = (predicate, array) => array.reduce(
  (list, el) => predicate(el) ? list.concat(el) : list,
  []
);

Tôi hy vọng điều này cung cấp một số bối cảnh hơn nữa cho cách sử dụng reduce.

Một bổ sung cho điều này, mà tôi chưa chia sẻ, là khi có kỳ vọng rằng các loại đầu vào và đầu ra đặc biệt có nghĩa là động, bởi vì các thành phần mảng là các hàm:

const compose = (...fns) => x =>
  fns.reduceRight((x, f) => f(x), x);

const hgfx = h(g(f(x)));
const hgf = compose(h, g, f);
const hgfy = hgf(y);
const hgfz = hgf(z);

1
+1 để giải thích rõ ràng về hệ thống loại. OTOH, tôi nghĩ bằng cách mở ra với ý tưởng rằng đó là một sự dị hình có thể sẽ khiến nhiều người ...
Periata Breatta

6

Đối với lần lặp đầu tiên 'a' sẽ là đối tượng đầu tiên trong mảng, do đó ax + bx sẽ trả về 1 + 2 tức là 3. Bây giờ trong lần lặp tiếp theo, 3 được trả về được gán cho a, vì vậy a là một số bây giờ gọi ax sẽ cho NaN.

Giải pháp đơn giản trước tiên là ánh xạ các số trong mảng và sau đó giảm chúng như dưới đây:

arr.map(a=>a.x).reduce(function(a,b){return a+b})

ở đây arr.map(a=>a.x)sẽ cung cấp một dãy số [1,2,4] hiện đang sử dụng .reduce(function(a,b){return a+b})sẽ đơn giản thêm các số này mà không có bất kỳ hassel nào

Một giải pháp đơn giản khác là chỉ cung cấp một số tiền ban đầu bằng 0 bằng cách gán 0 cho 'a' như dưới đây:

arr.reduce(function(a,b){return a + b.x},0)

5

Ở mỗi bước giảm của bạn, bạn sẽ không trả lại một {x:???}đối tượng mới . Vì vậy, bạn cần phải làm:

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})

hoặc bạn cần làm

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 

3
Ví dụ đầu tiên cần một giá trị mặc định (chẳng hạn như 0) khác "a" không được xác định trong lần lặp đầu tiên.
Griffin

2

Trong bước đầu tiên, nó sẽ hoạt động tốt vì giá trị asẽ là 1 và bsẽ là 2 nhưng vì 2 + 1 sẽ được trả về và trong bước tiếp theo, giá trị của bsẽ là giá trị trả về from step 1 i.e 3và do đó b.xsẽ không được xác định .. .and không xác định + anyNumber sẽ là NaN và đó là lý do tại sao bạn nhận được kết quả đó.

Thay vào đó, bạn có thể thử điều này bằng cách cho giá trị ban đầu là 0 tức là

arr.reduce(function(a,b){return a + b.x},0);


Vui lòng cập nhật điều này để cho thấy cách tiếp cận của bạn khác với những người khác.
kwelch

2

Nếu bạn có một đối tượng phức tạp với nhiều dữ liệu, như một mảng các đối tượng, bạn có thể thực hiện từng bước tiếp cận để giải quyết vấn đề này.

Ví dụ:

const myArray = [{ id: 1, value: 10}, { id: 2, value: 20}];

Đầu tiên, bạn nên ánh xạ mảng của bạn thành một mảng mới mà bạn quan tâm, nó có thể là một mảng giá trị mới trong ví dụ này.

const values = myArray.map(obj => obj.value);

Hàm gọi lại này sẽ trả về một mảng mới chỉ chứa các giá trị từ mảng ban đầu và lưu trữ nó trên các giá trị const. Bây giờ giá trị const của bạn là một mảng như thế này:

values = [10, 20];

Và bây giờ bạn đã sẵn sàng để thực hiện giảm của bạn:

const sum = values.reduce((accumulator, currentValue) => { return accumulator + currentValue; } , 0);

Như bạn có thể thấy, phương thức rút gọn thực hiện chức năng gọi lại nhiều lần. Đối với mỗi lần, nó lấy giá trị hiện tại của mục trong mảng và tính tổng với bộ tích lũy. Vì vậy, để tổng hợp chính xác, bạn cần đặt giá trị ban đầu của bộ tích lũy làm đối số thứ hai của phương thức rút gọn.

Bây giờ bạn có tổng const mới của bạn với giá trị là 30.


0

giảm chức năng lặp đi lặp lại trên một bộ sưu tập

arr = [{x:1},{x:2},{x:4}] // is a collection

arr.reduce(function(a,b){return a.x + b.x})

Dịch sang:

arr.reduce(
    //for each index in the collection, this callback function is called
  function (
    a, //a = accumulator ,during each callback , value of accumulator is 
         passed inside the variable "a"
    b, //currentValue , for ex currentValue is {x:1} in 1st callback
    currentIndex,
    array
  ) {
    return a.x + b.x; 
  },
  accumulator // this is returned at the end of arr.reduce call 
    //accumulator = returned value i.e return a.x + b.x  in each callback. 
);

trong mỗi lần gọi lại chỉ mục, giá trị của biến "tích lũy" được truyền vào tham số "a" trong hàm gọi lại. Nếu chúng ta không khởi tạo "bộ tích lũy", giá trị của nó sẽ không được xác định. Gọi không xác định.x sẽ cung cấp cho bạn lỗi.

Để giải quyết vấn đề này, hãy khởi tạo "bộ tích lũy" với giá trị 0 như câu trả lời của Casey đã trình bày ở trên.

Để hiểu được chức năng "giảm", tôi khuyên bạn nên xem mã nguồn của hàm này. Thư viện Lodash có chức năng giảm hoạt động giống hệt như chức năng "giảm" trong ES6.

Đây là liên kết: giảm mã nguồn


0

để trả lại một tổng của tất cả các xđạo cụ:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)

3
Cung cấp ngữ cảnh về lý do tại sao câu trả lời này tốt hơn sau đó những câu trả lời khác được chấp nhận hoặc nâng cao có thể giúp người dùng khi tìm giải pháp cho câu hỏi của họ.
Andrew

0

Bạn có thể sử dụng bản đồ và giảm chức năng

const arr = [{x:1},{x:2},{x:4}];
const sum = arr.map(n => n.x).reduce((a, b) => a + b, 0);

-1

Hàm giảm mảng có ba tham số tức là initValue (mặc định là 0), bộ tích lũy và giá trị hiện tại. Theo mặc định, giá trị của initValue sẽ là "0". được lấy bởi ắc quy

Chúng ta hãy xem điều này trong mã.

var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal ) ; 
// (remember Initialvalue is 0 by default )

//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks .
// solution = 7

Bây giờ cùng ví dụ với Giá trị ban đầu:

var initialValue = 10;
var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal,initialValue ) ; 
/
// (remember Initialvalue is 0 by default but now it's 10 )

//first iteration** : 10 +1 => Now accumulator =11;
//second iteration** : 11 +2 => Now accumulator =13;
//third iteration** : 13 + 4 => Now accumulator = 17;
No more array properties now the loop breaks .
//solution=17

Cũng áp dụng cho các mảng đối tượng (câu hỏi stackoverflow hiện tại):

var arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(acc,currVal){return acc + currVal.x}) 
// destructing {x:1} = currVal;
Now currVal is object which have all the object properties .So now 
currVal.x=>1 
//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks 
//solution=7

MỘT ĐIỀU ĐỂ HIỂU TRONG MIND là InitialValue theo mặc định là 0 và có thể được cung cấp bất cứ điều gì tôi có nghĩa là {}, [] và số


-1

    
  
 //fill creates array with n element
 //reduce requires 2 parameter , 3rd parameter as a length
 var fibonacci = (n) => Array(n).fill().reduce((a, b, c) => {
      return a.concat(c < 2 ? c : a[c - 1] + a[c - 2])
  }, [])
  console.log(fibonacci(8))


-2

bạn không nên sử dụng ax cho bộ tích lũy, thay vào đó bạn có thể làm như thế này `Array = [{x: 1}, {x: 2}, {x: 4}]

mảng.reduce (hàm (a, b) {a + bx}, 0) `

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.