Javascript reduce Fonksiyonu

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;

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.

Nesne Dönen reduce Fonksiyonu

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.

Büyük Veri Dizilerinde reduce Fonksiyonu

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

Opsiyonel reduce Parametreleri

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.

reduce ile Fonksiyonları Birleştirmek

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

İçiçe Nesneleri Güvenli Bir Şekilde reduce ile Kontrol Etmek

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.