Javascript mengurangi array objek

225

Katakanlah saya ingin menjumlahkan a.xuntuk setiap elemen di arr.

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

Saya punya alasan untuk percaya bahwa kapak tidak ditentukan pada beberapa titik.

Berikut ini berfungsi dengan baik

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

Apa yang saya lakukan salah dalam contoh pertama?

YXD
sumber
1
Juga, saya pikir maksud Anda arr.reduce(function(a,b){return a + b})dalam contoh kedua.
Jamie Wong
1
Terima kasih atas koreksinya. Saya menemukan pengurangan di sini: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
YXD
6
@Jamie Wong itu sebenarnya adalah bagian dari JavaScript 1.8
JaredMcAteer
@OriginalSyn yeah - baru saja melihatnya. Menarik, tetapi karena tidak memiliki dukungan asli sepenuhnya, implementasi masih penting ketika menjawab pertanyaan seperti ini.
Jamie Wong
3
Versi JavaScript hanyalah versi dari juru bahasa Firefox, membingungkan untuk merujuknya. Hanya ada ES3 dan ES5.
Raynos

Jawaban:

279

Setelah iterasi pertama Anda mengembalikan angka dan kemudian mencoba untuk mendapatkan properti xitu untuk ditambahkan ke objek berikutnya yang merupakan undefineddan matematika yang melibatkan undefinedhasil NaN.

coba kembalikan objek berisi xproperti dengan jumlah properti x dari parameter:

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}

Penjelasan ditambahkan dari komentar:

Nilai pengembalian setiap iterasi [].reducedigunakan sebagai avariabel dalam iterasi berikutnya.

Iterasi 1: a = {x:1}, b = {x:2}, {x: 3}ditugaskan untuk adi iterasi 2

Iterasi 2: a = {x:3}, b = {x:4}.

Masalah dengan contoh Anda adalah bahwa Anda mengembalikan angka literal.

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

Iterasi 1: a = {x:1}, b = {x:2}, // returns 3seperti adi iterasi berikutnya

Iterasi 2: a = 3, b = {x:2}kembaliNaN

Sejumlah literal 3tidak (biasanya) memiliki properti yang dinamakan xdemikian undefineddan undefined + b.xkembali NaNdan NaN + <anything>selaluNaN

Klarifikasi : Saya lebih suka metode saya daripada jawaban teratas lainnya di utas ini karena saya tidak setuju dengan gagasan bahwa melewatkan parameter opsional untuk dikurangi dengan angka ajaib untuk mengeluarkan angka primitif lebih bersih. Mungkin menghasilkan lebih sedikit baris yang ditulis tetapi jika itu kurang dibaca.

JaredMcAteer
sumber
7
Tentu saja mengurangi mengambil fungsi untuk melakukan operasi pada setiap elemen dalam sebuah array. Setiap kali ia mengembalikan nilai yang digunakan sebagai variabel 'a' berikutnya dalam operasi. Jadi iterasi pertama a = {x: 1}, b = {x: 2} kemudian iterasi kedua a = {x: 3} (nilai gabungan dari iterasi pertama), b = {x: 4}. Masalah dengan contoh Anda dalam iterasi kedua yang ia coba tambahkan 3.x + bx, 3 tidak memiliki properti yang disebut x sehingga kembali tidak terdefinisi dan menambahkan bahwa ke bx (4) dikembalikan Bukan Angka
JaredMcAteer
Saya mengerti penjelasannya, tetapi saya masih tidak melihat bagaimana {x: ...} mencegah iterasi 2 dari memanggil kapak? Apa artinya x? Saya mencoba menggunakan pendekatan ini dengan metode, dan sepertinya tidak berhasil
mck
sebenarnya, baca saja stackoverflow.com/questions/2118123/… dan pahami cara kerjanya .. tetapi bagaimana Anda melakukannya ketika Anda memiliki metode, bukan hanya atribut yang lurus?
mck
278

Cara yang lebih bersih untuk mencapai ini adalah dengan memberikan nilai awal:

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

Pertama kali fungsi anonim dipanggil, ia dipanggil dengan (0, {x: 1})dan kembali 0 + 1 = 1. Lain kali, ia dipanggil dengan (1, {x: 2})dan kembali 1 + 2 = 3. Kemudian dipanggil dengan (3, {x: 4}), akhirnya kembali 7.

Casey Chu
sumber
1
Ini adalah solusinya jika Anda memiliki nilai awal (parameter 2 pengurangan)
TJ.
Thanx, saya mencari petunjuk dengan argumen kedua reduce.
nilsole
Ini kurang kode dan lebih langsung dari jawaban yang diterima - apakah ada cara di mana jawaban yang diterima lebih baik dari ini?
jbyrd
1
Ini membantu saya untuk mengambil kasus ketika panjang array hanya 1 kadang-kadang.
HussienK
1
es6 way var arr = [{x: 1}, {x: 2}, {x: 4}]; biarkan a = arr.reduce ((acc, obj) => acc + obj.x, 0); console.log (a);
amar ghodke
76

TL; DR, tetapkan nilai awal

Menggunakan destrrukturisasi

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

Tanpa merusak

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

Dengan naskah

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

Mari kita coba metode penghancuran:

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

Kunci untuk ini adalah menetapkan nilai awal. Nilai kembali menjadi parameter pertama dari iterasi berikutnya.

Teknik yang digunakan dalam jawaban atas tidak idiomatis

Jawaban yang diterima mengusulkan TIDAK melewati nilai "opsional". Ini salah, karena cara idiomatik adalah bahwa parameter kedua selalu disertakan. Mengapa? Tiga alasan:

1. Berbahaya - Tidak melewatkan nilai awal berbahaya dan dapat membuat efek samping dan mutasi jika fungsi panggilan balik ceroboh.

Melihat

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

Namun jika kita melakukannya dengan cara ini, dengan nilai awal:

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

Sebagai catatan, kecuali jika Anda bermaksud untuk bermutasi objek asli, atur parameter pertama Object.assignke objek kosong. Seperti ini: Object.assign({}, a, b, c).

2 - Inferensi Jenis Yang Lebih Baik - Saat menggunakan alat seperti Typecript atau editor seperti VS Code, Anda mendapatkan manfaat dari memberitahu kompiler awal dan dapat menangkap kesalahan jika Anda melakukannya dengan salah. Jika Anda tidak menetapkan nilai awal, dalam banyak situasi mungkin tidak dapat menebak dan Anda bisa berakhir dengan kesalahan runtime menyeramkan.

3 - Hormati Functors - JavaScript bersinar terbaik saat anak fungsional dalamnya dilepaskan. Dalam dunia fungsional, ada standar tentang bagaimana Anda "melipat" atau reducearray. Ketika Anda melipat atau menerapkan katamorfisme ke array, Anda mengambil nilai array itu untuk membangun tipe baru. Anda perlu mengkomunikasikan tipe yang dihasilkan - Anda harus melakukan ini bahkan jika tipe terakhir adalah nilai-nilai dalam array, array lain, atau jenis lainnya.

Mari kita pikirkan dengan cara lain. Dalam JavaScript, fungsi dapat diedarkan seperti data, ini adalah cara kerja callback, apa hasil dari kode berikut?

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

Apakah akan mengembalikan nomor? Sebuah Objek? Ini membuatnya lebih jelas

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

Baca lebih lanjut tentang spesifikasi pemrograman fungsional di sini: https://github.com/fantasyland/fantasy-land#foldable

Lebih banyak latar belakang

The reduceMetode membutuhkan dua parameter,

Array.prototype.reduce( callback, initialItem )

The callbackfungsi mengambil parameter berikut

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

Untuk iterasi pertama,

  • Jika initialItemdisediakan, reducefungsi meneruskan initialItemsebagai accumulatordan item pertama dari array sebagai itemInArray.

  • Jika initialItemini tidak disediakan, reducefungsi melewati item pertama dalam array sebagai initialItemdan item kedua dalam array sebagai itemInArrayyang dapat membingungkan perilaku.

Saya mengajar dan merekomendasikan untuk selalu menetapkan nilai awal pengurangan.

Anda dapat melihat dokumentasi di:

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

Semoga ini membantu!

Babakness
sumber
1
Destrukturisasi adalah apa yang saya inginkan. Bekerja seperti pesona. Terima kasih!
AleksandrH
24

Orang lain telah menjawab pertanyaan ini, tetapi saya pikir saya akan menggunakan pendekatan lain. Daripada langsung menjumlahkan kapak, Anda dapat menggabungkan peta (dari kapak ke x) dan mengurangi (untuk menambahkan x's):

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

Memang, ini mungkin akan sedikit lebih lambat, tapi saya pikir itu layak disebut sebagai opsi.

RHSeeger
sumber
8

Untuk memformalkan apa yang telah ditunjukkan, reducer adalah katamorphism yang mengambil dua argumen yang mungkin merupakan tipe yang sama secara kebetulan, dan mengembalikan tipe yang cocok dengan argumen pertama.

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

Itu berarti bahwa badan peredam harus tentang konversi currentValuedan nilai saat ini accumulatorke nilai baru accumulator.

Ini bekerja dengan cara yang langsung, ketika menambahkan, karena akumulator dan nilai elemen keduanya bertipe sama (tetapi melayani tujuan yang berbeda).

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

Ini hanya berfungsi karena semuanya angka.

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

Sekarang kami memberikan nilai awal kepada agregator. Nilai awal harus menjadi tipe yang Anda harapkan sebagai agregator (tipe yang Anda harapkan sebagai nilai akhir), dalam sebagian besar kasus. Meskipun Anda tidak dipaksa untuk melakukan ini (dan tidak seharusnya), penting untuk diingat.

Setelah Anda tahu itu, Anda dapat menulis pengurangan yang berarti untuk masalah hubungan n: 1 lainnya.

Menghapus kata yang diulang:

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

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

Memberikan hitungan semua kata yang ditemukan:

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

Mengurangi array array, menjadi satu flat array:

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

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

Setiap kali Anda ingin beralih dari berbagai hal, ke nilai tunggal yang tidak cocok dengan 1: 1, pengurangan adalah sesuatu yang mungkin Anda pertimbangkan.

Faktanya, peta dan filter keduanya dapat diimplementasikan sebagai pengurangan:

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,
  []
);

Saya harap ini memberikan beberapa konteks lebih lanjut tentang cara menggunakan reduce.

Satu tambahan untuk ini, yang belum saya bahas, adalah ketika ada harapan bahwa tipe input dan output secara khusus dimaksudkan untuk menjadi dinamis, karena elemen array adalah fungsi:

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);
Norguard
sumber
1
+1 untuk menjelaskannya dengan jelas dalam hal sistem tipe. OTOH, saya pikir dengan membuka dengan gagasan bahwa itu adalah katamorfisme mungkin menyangkal banyak orang ...
Periata Breatta
6

Untuk iterasi pertama 'a' akan menjadi objek pertama dalam array, maka ax + bx akan mengembalikan 1 + 2 yaitu 3. Sekarang di iterasi berikutnya, 3 dikembalikan ditugaskan ke, jadi a adalah nomor sekarang n memanggil kapak akan memberi NaN.

Solusi sederhana adalah pertama-tama memetakan angka dalam array dan kemudian menguranginya seperti di bawah ini:

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

di sini arr.map(a=>a.x)akan memberikan array angka [1,2,4] sekarang menggunakan .reduce(function(a,b){return a+b})akan dengan mudah menambahkan angka-angka ini tanpa hassel

Solusi sederhana lainnya adalah dengan memberikan jumlah awal sebagai nol dengan menetapkan 0 ke 'a' seperti di bawah ini:

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

Pada setiap langkah pengurangan Anda, Anda tidak mengembalikan {x:???}objek baru . Jadi, Anda juga perlu melakukan:

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

atau yang perlu Anda lakukan

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 
Jamie Wong
sumber
3
Contoh pertama membutuhkan nilai default (seperti 0) jika tidak, "a" tidak ditentukan dalam iterasi pertama.
Griffin
2

Pada langkah pertama, itu akan berfungsi dengan baik karena nilai aakan 1 dan bahwa bakan 2 tetapi karena 2 + 1 akan dikembalikan dan pada langkah berikutnya nilai bakan menjadi nilai balik from step 1 i.e 3dan dengan demikian b.xakan tidak terdefinisi .. .dan undefined + anyNumber akan menjadi NaN dan itulah mengapa Anda mendapatkan hasil itu.

Sebagai gantinya Anda dapat mencoba ini dengan memberikan nilai awal sebagai nol yaitu

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

Nitesh Ranjan
sumber
Perbarui ini untuk menunjukkan bagaimana pendekatan Anda berbeda dari yang lain.
kwelch
2

Jika Anda memiliki objek yang kompleks dengan banyak data, seperti array objek, Anda dapat mengambil pendekatan langkah demi langkah untuk menyelesaikannya.

Untuk misalnya:

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

Pertama, Anda harus memetakan array Anda ke array baru yang Anda minati, bisa berupa array nilai baru dalam contoh ini.

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

Fungsi panggilan balik ini akan mengembalikan array baru yang hanya berisi nilai-nilai dari array asli dan menyimpannya pada nilai const. Sekarang const nilai Anda adalah array seperti ini:

values = [10, 20];

Dan sekarang Anda siap melakukan pengurangan Anda:

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

Seperti yang Anda lihat, metode pengurangan menjalankan fungsi panggilan balik beberapa kali. Untuk setiap waktu, dibutuhkan nilai item saat ini dalam array dan jumlah dengan akumulator. Jadi untuk menjumlahkannya dengan benar, Anda perlu mengatur nilai awal akumulator Anda sebagai argumen kedua dari metode pengurangan.

Sekarang Anda memiliki const sum baru Anda dengan nilai 30.

João Ramires
sumber
0

mengurangi fungsi yang berulang pada koleksi

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

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

diterjemahkan menjadi:

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

selama setiap panggilan balik indeks, nilai variabel "akumulator" dilewatkan ke dalam parameter "a" dalam fungsi panggilan balik. Jika kita tidak menginisialisasi "akumulator", nilainya akan tidak ditentukan. Memanggil undefined.x akan memberi Anda kesalahan.

Untuk mengatasi ini, inisialisasi "akumulator" dengan nilai 0 seperti jawaban Casey ditunjukkan di atas.

Untuk memahami seluk-beluk fungsi "perkecil", saya sarankan Anda melihat kode sumber fungsi ini. Pustaka Lodash memiliki fungsi pengurangan yang bekerja persis sama dengan fungsi "perkecil" di ES6.

Inilah tautannya: kurangi kode sumber

Deen John
sumber
0

untuk mengembalikan jumlah semua xalat peraga:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)
Black Jack
sumber
3
Memberikan konteks mengapa jawaban ini lebih baik daripada jawaban yang diterima atau terunggul dapat membantu pengguna saat mencari solusi untuk pertanyaan mereka.
Andrew
0

Anda bisa menggunakan peta dan mengurangi fungsi

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

Array mengurangi fungsi mengambil tiga parameter yaitu, initialValue (default 0), akumulator dan nilai saat ini. Secara default nilai initialValue akan menjadi "0". yang diambil oleh akumulator

Mari kita lihat ini dalam kode.

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

Sekarang contoh yang sama dengan Nilai awal:

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

Hal yang sama berlaku untuk array objek juga (pertanyaan stackoverflow saat ini):

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

SATU HAL YANG DIPEROLEH DALAM MIND adalah InitialValue secara default adalah 0 dan dapat diberikan apa pun yang saya maksud {}, [] dan angka

M.Hee
sumber
-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))

Rajesh Bose
sumber
-2

Anda tidak boleh menggunakan kapak untuk akumulator, Alih-alih Anda dapat melakukan seperti ini `arr = [{x: 1}, {x: 2}, {x: 4}]

arr.reduce (fungsi (a, b) {a + bx}, 0) `

RAks BM
sumber