Javascript reduce Fonksiyonu

İbrahim Kürce
May 25, 2017 · 5 min read

ECMAScript 5.1 ile gelen ve diziler üzerinde işlem yapan reduce fonksiyonunu tanıtmaya çalışacağız. Bu konu ile ilgili yazarken şu adresteki eğitimden yararlandım.

Temel kullanımını örnek kod ile gösterelim,

let data = [2, 4, 6, 8];
const reducer = function (accumulator, item) {
return accumulator + item;
};

const firstValue = 0;
const sum = data.reduce(reducer, firstValue);
console.log("Toplam değer: ", sum);
// Toplam değer: 20

reduce fonksiyonunun bu basit kullanımı 2 parametre alır; ilk parametre işlemin yapılacağı bir fonksiyon (reducer fonksiyonu), diğer parametre ise ilk değerdir.

reduce fonksiyonu, data dizisinin her bir elemanı kadar reducer fonksiyonunu çağırır. Bu işlemin sonucunu kümülatif olarak hesaplar. Yapılan her işlemin sonucu bir sonraki fonksiyon çağırımına ilk parametre olarak aktarılır. Şöyle gösterebiliriz;

Image for post
Image for post

Eğer reduce fonksiyonuna ilk değer verilmezse, fonksiyon ilk elemandan başlayarak hesaplamaya devam eder ama bu bazen istenmeyen davranışlara yol açabilir.

Bu yüzden, reduce fonksiyonuna her zaman ilk değer vermek hata çıkmasını azaltır.

reduce fonksiyonu ile dizilerde gruplama yapabilir ve sonucu nesne olarak dönebiliriz. Örneğin;

let books = [
“javascript”,
“javascript”,
“clojure”,
“clojure”,
“clojure”,
“java”,
“kotlin”,
“kotlin”,
];
const firstValue = {};
const reducer = function(obj, count){
if (!obj[count]){
obj[count] = 1;
} else {
obj[count] = obj[count] + 1;
}
return obj;
};
const result = books.reduce(reducer, firstValue);
console.log(“Okuduğum kitaplar: “, result);
//Okuduğum kitaplar: { javascript:2, clojure:3, java: 1, kotlin: 2 }

Burada ilk örnekten farklı olarak fonksiyonumuzun nesne dönmesini istiyoruz. Her bir eleman için daha önce nesnemizde yoksa ilk değer olarak 1 set ediyoruz, var ise de değerini 1 artırıyoruz. Böylelikle sonuç nesnemiz ortaya çıkıyor.

Eleman sayısı çok olan diziler üzerinde işlem yaparken, birden çok filter veya map gibi fonksiyonlar yerine tek bir reduce kullanmak daha hızlı sonuç almamızı sağlayabilir. Örneğin;

let bigArray = [];
for (let i = 0; i < 1000000; i++) {
bigArray[i] = i;
}

console.time("bigNormal");
let mappedBigArray = bigArray.filter(function (val) {
return val % 2 === 0;
}).map(function (val) {
return val * 2;
});

console.timeEnd("bigNormal");
// bigNormal: 463.996ms

console.time("bigReduce");
let reducedBigArray = bigArray.reduce(function (acc, val) {
if (val % 2 === 0) {
acc.push(val * 2);
}
return acc;
}, []);

console.timeEnd("bigReduce");
// bigReduce: 37.710ms

1 milyon elemanlık bir dizimizin olduğunu varsayalım. Önce filter fonksiyonu ile çift olanlarını bulup sonra bunların 2 katını almak istesek, gördüğünüz gibi 463 ms gibi bir sürede bu işlemi tamamlıyoruz. Çünkü önce 1 milyonluk dizi üzerinde dönüp daha sonra 500 binlik dizi üzerinde dönmemiz gerekiyor. Bununla birlikte aynı işlemi tek bir reduce fonksiyonu ile yapmış olsaydık, 37 ms gibi bir süre ortaya çıkıyor. Çünkü tek defada 1 milyonluk dizi üzerinde işlemi yapmış olduk. Bu da bize 10 kat gibi bir performans artışını sağladı.

Daha önce reduce fonksiyonun parametre aldığı reducer fonksiyonun ilk iki parametresini görmüştük. Şimdi diğer iki parametreye bakalım. Örnek kod üzerinden ilerlersek,

function reducer(acc, value, index, array) {
let interValue = acc + value;
if (index === array.length — 1) {
return interValue / array.length;
}
return interValue;
}

let data = [1, 2, 3, 4, 5, 6];
const mean = data.reduce(reducer, 0);
console.log(“Ortalama:”, mean);
// Ortalama: 3.5

reducer fonksiyonun diğer 2 parametresi; 0’dan başlayarak her dönen elemanın indeksi(3.parametre index) ve dizinin kendisi(4.parametre array).

Burada son indekse kadar toplam almaya devam ediyoruz, son indekste ise farklı bir işlem ile ortalamasını hesaplıyoruz.

Böylece saf bir şekilde direk olarak ortalamayı hesaplayan bir fonksiyon yazmış oluruz.

Elimizde bir değerimiz olduğu ve bunu çeşitli fonksiyonlardan geçirdiğimizi düşünelim. Örneğin;

function increment(input) {
return input + 1;
}
function decrement(input) {
return input - 1;
}
function double(input) {
return input * 2;
}

let initValue = 1;
let incrementValue = increment(initValue);
let doubledValue = double(incrementValue);
let finalValue = decrement(doubledValue);

console.log(“Son değer:”, finalValue);
// Son değer: 3

Burada artırma, azaltma ve ikiye katlama gibi çeşitli fonksiyonlarımız var. Sırayla bu fonksiyonları çağırırken, bunların sırasını karıştırmak veya birinden aldığımız değeri diğerine yanlış şekilde pas etmek sıkça yapabileceğimiz hatalardan. Bunu önlemek için reduce fonksiyonundan yararlanabiliriz.

Bunun için fonksiyonlardan oluşan bir pipeline dizisi tanımlayabilir ve bunu reduce fonksiyonuna sırayla parametre geçebiliriz. Diğer 3 fonksiyonun aynı şekilde olduğunu varsayalım. Son kısmı şu şekilde değiştirebiliriz;

let initValue = 1;
const pipeline = [
increment,
double,
decrement
];

const finalValue = pipeline.reduce(function (acc, fn) {
return fn(acc);
}, initValue);

console.log(“Son değer:”, finalValue);
// Son değer: 3

Böyle yaparak, daha bakımı kolay bir kod ortaya çıkarmış oluruz. Yapılacak işlemlerde herhangi bir değişiklik olursa sadece pipeline dizisini değiştirmemiz bize yetecektir. Birden fazla artırma, ikiye katlama ve azaltma yapmak istesek şu değişiklik yeterli olacaktır.

const pipeline = [
increment,
increment,
double,
double,
decrement,
decrement,
decrement,
decrement
];

Bazı büyük nesneler üzerinde işlem yaparken, olmayan nesneler üzerinde işlem yapmamaya dikkat etmeliyiz. Dikkat etmezsek sık sık “undefined” hatası alabiliriz. Şu örneğe bakacak olursak,

let luke = {
name: "luke",
jedi: true,
parents: {
father: {
jedi: true
},
mother: {
jedi: false
}
}
};
let han = {
name: "han",
jedi: false,
parents: {
father: {
jedi: false
},
mother: {
jedi: false
}
}
};
let anakin = {
name: "anakin",
jedi: true,
parents: {
mother: {
jedi: false
}
}
};
const characters = [luke, han, anakin];
characters.forEach(function (character) {
console.log(character.name + "'nın babası bir jedi:", character.parents.father.jedi);
});

İlk iki nesne için düzgün şekilde çalıştıktan sonra 3.nesnenin father nesnesi boş olduğu için aşağıdaki gibi “undefined” hatası alırız.

/*
luke’nın babası bir jedi: true
han’nın babası bir jedi: false
reduce.js:36
console.log(character.name + “‘nın babası bir jedi:”, character.parents.father.jedi);
^

TypeError: Cannot read property ‘jedi’ of undefined
at reduce.js:36:83
*/

Peki bunu reduce kullanarak nasıl çözebiliriz. Son kısmı şu şekilde reduce fonksiyonuna uyarlayabiliriz;

function fatherWasJedi(character) {
let path = "parents.father.jedi";
return path.split(".").reduce(function (obj, field) {
if (obj) {
return obj[field];
}
return false;
}, character);
}

characters.forEach(function (character) {
console.log(character.name + "'nın babası bir jedi:",
fatherWasJedi(character));
});
// luke'nın babası bir jedi: true
// han'nın babası bir jedi: false
// anakin'nın babası bir jedi: false

Burada gitmek istediğimiz nesne yolunu “path” değişkeninde saklarız. Bunu nokta ile ayırarak her bir ayırdığımız alan üzerinde character nesnesini tararız. Eğer bu nesnenin ilgili alanı var ise onu döneriz ve ordan devam ederiz, alanı bulamazsa da false döneriz.

han” nesnesi üzerinden kodun çalışmasına bakacak olursak,

let han = {
name: "han",
jedi: false,
parents: {
father: {
jedi: false
},
mother: {
jedi: false
}
}
};

İlk önce bu nesneyi fonksiyona parametre olarak veririz, “parents” alanı var mı diye kontrol eder ve bunu geriye döndürür,

     parents: {
father: {
jedi: false
},
mother: {
jedi: false
}
}

Daha sonra “father” alanı üzerinden kontrolü yapar ve bulursak bunu döneriz.

         father: {
jedi: false
}

En sonda ise “jedi” alanını kontrol eder ve varsa bunun değerini döneriz. Eğer bu işlemin herhangi bir yerinde nesne bulunamazsa false olarak döneriz.

Bu yazımızda da reduce fonksiyonunun kullanışlı olabileceği bazı örnekler vermeye çalıştık.

Bir sonraki yazımızda görüşmek üzere.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store