Nesne dizisinde Javascript azaltma


227

Diyelim ki a.xiçindeki her öğe için toplamak istiyorum arr.

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

Baltanın bir noktada tanımsız olduğuna inanmak için nedenim var.

Aşağıdaki iyi çalışır

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

İlk örnekte neyi yanlış yapıyorum?


1
Ayrıca, arr.reduce(function(a,b){return a + b})ikinci örnekte kastettiğini düşünüyorum .
Jamie Wong

1
Düzeltme için teşekkürler. Ben burada azaltmak geldi: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
YXD

6
@Jamie Wong aslında JavaScript
1.8'in

@OriginalSyn evet - sadece gördüm. İlginç, ancak tam yerel desteğe sahip olmadığı için, uygulama bu tür soruları cevaplarken hala önemlidir.
Jamie Wong

3
JavaScript sürümleri yalnızca Firefox yorumlayıcısının sürümleridir, bunlara başvurmak kafa karıştırıcıdır. Sadece ES3 ve ES5 var.
Raynos

Yanıtlar:


279

İlk yinelemeden sonra, bir sayı döndürüyorsunuz ve daha sonra bir xsonraki nesneye undefinedve undefinedsonuçları içeren matematiklere eklemek için onun özelliğini almaya çalışıyorsunuzNaN .

xparametrelerin x özelliklerinin toplamına sahip bir özellik içeren bir nesneyi döndürmeyi deneyin :

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}

Yorumlardan açıklama eklendi:

Olarak [].reducekullanılan her bir yinelemenin dönüş değeria sonraki yinelemede değişken .

Yineleme 1: a = {x:1}, b = {x:2}, {x: 3}atanana İterasyon 2'de

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

Örneğinizin sorunu, bir sayı değişmez değeri döndürmenizdir.

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

Yineleme 1: a = {x:1}, b = {x:2}, // returns 3olaraka bir sonraki tekrarında

Yineleme 2: a = 3,b = {x:2} dönerNaN

Bir dizi değişmezi 3(genellikle) olarak adlandırılan bir özelliği yok xo yüzden undefinedve undefined + b.xiadeler NaNve NaN + <anything>her zamanNaN

Açıklama : Bir ilkel sayı almak için sihirli bir sayı ile azaltmak için isteğe bağlı bir parametre geçirmenin daha temiz olduğu fikrine katılmıyorum olarak, bu konudaki diğer üst cevaba göre yöntemimi tercih ederim. Daha az satır yazılmasına neden olabilir, ancak imo daha az okunabilir.


7
Tabii azaltma, bir dizideki öğelerin her biri üzerinde işlem gerçekleştirme işlevini alır. Her seferinde işlemde bir sonraki 'a' değişkeni olarak kullanılan bir değer döndürür. Bu nedenle ilk yineleme a = {x: 1}, b = {x: 2} sonra ikinci yineleme a = {x: 3} (ilk yinelemenin birleşik değeri), b = {x: 4}. 3.x + bx eklemeye çalıştığı ikinci yinelemede örneğinizle ilgili sorun, 3, x adında bir özelliğe sahip değil, bu yüzden tanımsız döndü ve bunu
bx'e

Açıklamayı anlıyorum, ancak hala {x: ...} yineleme 2'nin balta çağırmasını nasıl engellediğini göremiyorum? Bu x ne anlama geliyor? Bu yöntemi bir yöntemle kullanmayı denedim ve işe yaramıyor gibi görünüyor
mck

aslında, stackoverflow.com/questions/2118123/… 'ı okuyun ve nasıl çalıştığını anlayın .. ancak düz özellikler yerine bir yönteminiz olduğunda bunu nasıl yapardınız?
mck

Bu durumda, akümülatör parametresinin xçalışması için beklenen anahtara sahip olması gerekir.
Wallace Sidhrée

279

Bunu başarmanın daha temiz bir yolu, bir başlangıç ​​değeri sağlamaktır:

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

Anonim işlev ilk kez çağrıldığında çağrılır (0, {x: 1})ve geri döner 0 + 1 = 1. Bir dahaki sefere ile çağrılır (1, {x: 2})ve geri döner 1 + 2 = 3. Sonra denir (3, {x: 4}), sonunda geri döner 7.


1
Başlangıç ​​değeriniz (ikinci azaltma parametresi)
TJ.

Thanx, ikinci argüman ile ipucu arıyordum reduce.
nilsole

1
Bu, kabul edilen cevaptan daha az kod ve daha doğrudan - kabul edilen cevabın bundan daha iyi olduğu herhangi bir yol var mı?
jbyrd

1
Bu, dizi uzunluğu bazen sadece 1 olduğunda davayı almama yardımcı oldu.
HussienK

1
es6 yönlü var arr = [{x: 1}, {x: 2}, {x: 4}]; a = arr.reduce ((acc, obj) => acc + obj.x, 0) olsun; console.log (a) '
amar ghodke

77

TL; DR, başlangıç ​​değerini ayarlayın

kullanılması strüktür

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

Yıkmadan

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

Daktilo Yazısı ile

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

Yıkım yöntemini deneyelim:

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

Bunun anahtarı başlangıç ​​değerini belirlemektir. Dönüş değeri, bir sonraki yinelemenin ilk parametresi olur.

Üst cevapta kullanılan teknik deyimsel değil

Kabul edilen cevap "isteğe bağlı" değeri geçmemeyi önerir. Deyimsel yol her zaman ikinci parametrenin zaman dahil edilmesidir. Neden? Üç neden:

1. Tehlikeli - Başlangıç ​​değerinden geçmemek tehlikelidir ve geri arama işlevi dikkatsizse yan etkiler ve mutasyonlar oluşturabilir.

Seyretmek

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

Ancak bunu ilk değeriyle bu şekilde yapmış olsaydık:

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

Kayıt için, orijinal nesneyi değiştirmeyi düşünmüyorsanız, ilk parametresini Object.assignboş bir nesneye ayarlayın. Bunun gibi:Object.assign({}, a, b, c) .

2 - Daha İyi Tür Çıkarımı - Typescript gibi bir araç veya VS Code gibi bir düzenleyici kullanırken, derleyiciye baş harfini söylemenin avantajını elde edersiniz ve yanlış yaparsanız hataları yakalayabilirsiniz. Başlangıç ​​değerini ayarlamazsanız, birçok durumda tahmin edemeyebilirsiniz ve ürpertici çalışma zamanı hatalarıyla sonuçlanabilirsiniz.

3 - Functorlara Saygı - İç işlevsel çocuğu serbest bırakıldığında JavaScript en iyi şekilde parlar. İşlevsel dünyada, reducebir diziyi nasıl "katladığınız" konusunda bir standart vardır . Bir katamorfizma katladığınızda veya uyguladığınızda , yeni bir tür oluşturmak için bu dizinin değerlerini alırsınız. Ortaya çıkan türü bildirmeniz gerekir - son tür dizideki, başka bir dizideki veya başka bir türdeki değerlerde olsa bile bunu yapmalısınız.

Bunu başka bir şekilde düşünelim. JavaScript'te, işlevler veri gibi dolaşabilir, geri aramalar bu şekilde çalışır, aşağıdaki kodun sonucu nedir?

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

Bir sayı döndürecek mi? Bir obje? Bu daha net hale getirir

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

Fonksiyonel programlama spesifikasyonu hakkında daha fazla bilgiyi buradan edinebilirsiniz: https://github.com/fantasyland/fantasy-land#foldable

Biraz daha arka plan

reduceYöntem, iki parametre alır

Array.prototype.reduce( callback, initialItem )

callbackİşlev aşağıdaki parametreleri alır

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

İlk yineleme için,

  • Sağlanırsa initialItem, reduceişlev dizinin . Öğesini ve dizinin ilk öğesini initialItemolarak geçer .accumulatoritemInArray

  • Eğer initialItembir değil mesafede, reduceişlev olarak dizideki ilk madde geçer initialItemve benzeri gibi bir dizi ikinci ürünün itemInArraydavranışı kafa karıştırıcı olabilir.

Her zaman azaltmanın başlangıç ​​değerini ayarlamayı öğretir ve öneririm.

Dokümanlara şu adresten bakabilirsiniz:

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

Bu yardımcı olur umarım!


1
Yıkım tam da istediğim şeydi. Tıkır tıkır çalışıyor. Teşekkür ederim!
AleksandrH

24

Diğerleri bu soruyu yanıtladı, ama başka bir yaklaşımla fırlatacağımı düşündüm. Doğrudan toplama baltasına gitmek yerine, bir haritayı birleştirebilir (baltadan x'e) ve azaltabilir (x'leri eklemek için):

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

Kuşkusuz, muhtemelen biraz daha yavaş olacak, ancak bir seçenek olarak bahsetmeye değer olduğunu düşündüm.


8

Belirtileni resmileştirmek için, bir indirgeyici tesadüfle aynı tipte olabilecek iki argüman alan ve ilk argümanla eşleşen bir tür döndüren bir katamorfizmdir.

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

Bu, redüktörün gövdesinin dönüştürülme currentValueve akım değerinin accumulatoryeninin değerine çevrilmesi gerektiği anlamına gelir accumulator.

Akümülatör ve eleman değerlerinin her ikisi de aynı tipte olduğu için (ancak farklı amaçlara hizmet eder), bu eklerken basit bir şekilde çalışır.

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

Bu sadece işe yarıyor çünkü hepsi rakam.

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

Şimdi toplayıcıya bir başlangıç ​​değeri veriyoruz. Başlangıç ​​değeri, vakaların büyük çoğunluğunda toplayıcının olmasını beklediğiniz tür olmalıdır (son değer olarak çıkmayı beklediğiniz tür). Bunu yapmak zorunda kalmasanız da (olmamalısınız), aklınızda bulundurmanız önemlidir.

Bunu öğrendikten sonra, diğer n: 1 ilişki problemleri için anlamlı indirimler yazabilirsiniz.

Tekrarlanan kelimeleri kaldırma:

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

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

Bulunan tüm kelimelerin sayısını sağlama:

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

Bir dizi diziyi tek bir düz diziye azaltmak:

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

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

Bir dizi şeyden, 1: 1 ile eşleşmeyen tek bir değere gitmek istediğinizde, azaltmayı düşünebileceğiniz bir şeydir.

Aslında, hem harita hem de filtre indirgeme olarak uygulanabilir:

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

Umarım bu nasıl kullanılacağı hakkında daha fazla bağlam sağlar reduce .

Buna henüz girmediğim bir ek, girdi ve çıktı türlerinin özellikle dinamik olması gerektiği beklentisi olduğunda, dizi öğeleri işlevlerdir:

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
Tip sistemi açısından açık bir şekilde açıklamak için +1. OTOH, bence bu bir katamorfizma birçok insan için rahatsız edici olabilir ...
Periata Breatta

6

İlk yineleme için 'a' dizideki ilk nesne olacaktır, bu yüzden ax + bx 1 + 2 yani 3 döndürecektir. NaN verecek.

Basit çözüm, önce dizideki sayıları eşlemek ve ardından aşağıdaki gibi azaltmaktır:

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

burada arr.map(a=>a.x)şimdi kullanan bir dizi sayı [1,2,4] sağlayacak.reduce(function(a,b){return a+b}) herhangi bir hassel olmadan bu sayıları basit bir şekilde ekleyeceksiniz

Başka bir basit çözüm, aşağıdaki gibi 'a' ya 0 atayarak ilk toplamı sıfır olarak sağlamaktır:

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

5

Azaltmanızın her adımında yeni bir {x:???}nesne döndürmüyorsunuz. Yani ya yapmanız gerekenler:

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

ya da yapman gerek

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

3
İlk örnek varsayılan bir değere (0 gibi) ihtiyaç duyar, aksi takdirde ilk yinelemede "a" tanımsızdır.
Griffin

2

İlk adımda, değeri a1 olacak ve değeri b2 olacak, ancak 2 + 1 döndürülecek ve bir sonraki adımda değeri bdönüş değeri olacak from step 1 i.e 3ve böylece b.xtanımlanmayacaktır. .ve undefined + anyNumber NaN olacak ve bu yüzden bu sonucu alıyorsunuz.

Bunun yerine ilk değeri sıfır olarak vererek bunu deneyebilirsiniz.

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


Yaklaşımınızın diğerlerinden nasıl farklı olduğunu göstermek için lütfen bunu güncelleyin.
kwelch

2

Bir nesne dizisi gibi çok fazla veri içeren karmaşık bir nesneniz varsa, bunu çözmek için adım adım yaklaşabilirsiniz.

Örneğin:

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

İlk olarak, dizinizi ilgilendiğiniz yeni bir diziye eşlemelisiniz, bu örnekte yeni bir değer dizisi olabilir.

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

Bu geri arama işlevi, yalnızca orijinal diziden değerler içeren yeni bir dizi döndürür ve değerleri const üzerinde saklar. Şimdi, const değerleriniz şöyle bir dizidir:

values = [10, 20];

Ve şimdi azaltma işleminizi gerçekleştirmeye hazırsınız:

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

Gördüğünüz gibi, azaltma yöntemi geri arama işlevini birçok kez yürütür. Her seferinde, dizideki öğenin geçerli değerini alır ve akümülatörle toplar. Bu nedenle, düzgün bir şekilde toplamak için, akümülatörünüzün başlangıç ​​değerini, azaltma yönteminin ikinci argümanı olarak ayarlamanız gerekir.

Şimdi yeni const toplamınız 30'dur.


0

toplama üzerinde yineleme işlevi yineleme

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

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

Çevirir:

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

her indeks geri araması sırasında, "akümülatör" değişkeninin değeri geri arama fonksiyonundaki "a" parametresine geçirilir. Eğer "akümülatörü" başlatmazsak, değeri tanımlanmayacaktır. Undefined.x çağrısı size hata verir.

Bunu çözmek için, Casey'nin cevabının yukarıda gösterildiği gibi 0 değeriyle "akümülatörü" başlatın.

"Azalt" işlevinin girişlerini anlamak için, bu işlevin kaynak koduna bakmanızı öneririm. Lodash kütüphanesi, ES6'daki "azaltma" işleviyle tamamen aynı şekilde çalışan azaltma işlevine sahiptir.

Bağlantı: kaynak kodunu azaltın


0

tüm xaksesuarların toplamını döndürmek için :

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

3
Bu cevabın neden kabul edilen veya onaylanan diğer cevaplardan daha iyi olduğu konusunda bağlam sağlamak, sorularına çözüm ararken kullanıcılara yardımcı olabilir.
Andrew

0

Haritayı kullanabilir ve işlevleri azaltabilirsiniz

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

-1

Dizi azaltma işlevi üç parametre alır: initialValue (varsayılan değer 0), akümülatör ve akım değeri. Varsayılan olarak initialValue değeri "0" olur. akümülatör tarafından alınan

Bunu kodda görelim.

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

Şimdi ilk Değer ile aynı örnek:

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

Aynı şey nesne dizileri için de geçerlidir (mevcut yığın akışı sorusu):

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

ZİHİNDE ÇIPLAK OLACAK BİR ŞEY Başlangıç ​​Değeri varsayılan olarak 0'dır ve {}, [] ve sayı demek istediğim bir şey verilebilir


-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

akümülatör için balta kullanmamalısınız, bunun yerine bunu yapabilirsiniz `arr = [{x: 1}, {x: 2}, {x: 4}]

arr.reduce (işlev (a, b) {a + bx}, 0) `

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.