JavaScript ile Fonksiyonel Programlama: Map, Filter, Reduce
Devnot’taki bir önceki yazımda EcmaScript 6 üzerinden JavaScript ile fonksiyonel programlama konusunu ele almıştım. Bu yazıda da uygulama geliştiricilerinin günlük hayatta veri alıp verirken en çok diziler(array) üzerindeki fonksiyonlardan bahsedeceğiz. Günlük programlama rutininde for
veya while
gibi döngü ifadeleri ile dizi içerisindeki veriler arasında gezilip ilgili özellikler değiştirilerek sonuç ekrana basılmaktadır. Fakat for
döngüsünün ne iş yaptığı, süslü parantezler içerisine bakmadan herhangi bir ipucu vermemektedir. Bu nedenle her for döngüsüyle karşılaşıldığında dikkatli bir şekilde içeriği okunarak ne iş yaptığı anlamaya çalışılır. Örneğin aşağıdaki kodu ele alalım:
var makaleler = [
"Svelte Nedir? React ve Vue'nün Yerini Alabilir mi?",
"Rust Programlama Diline Giriş",
"Kaos Mühendisliği (Chaos Engineering) Nedir?",
"Dependency Injection ve Ninject",
"React Native ve Hooks ile Haber Uygulaması Yapımı",
"Yeni Başlayanlar İçin Travis CI"
];
for (var i = 0; i < makaleler.length; i++) {
/*
* Uzun bir for döngüsü
*
*/
}
Şimdi buradaki kodda for’un içeriğinin çok uzun olduğunu ve tümünü göz gezdirmediğimizi varsayalım. Kod ilk bakışta ne yapıyor gibi görünüyor? makaleler
dizisi üzerinde gezip bir şeyler yaptığını düşünüyoruz ama bu konuda herhangi bir ipucu vermiyor.
Bu gibi nedenlerle JavaScript’te dizi üzerinde yapılabilecek işlemler için map
, filter
ve reduce
gibi varsayılan fonksiyonlar bulunmaktadır. Bu fonksiyonlar, dizi üzerinde projection (haritalama), filtering (filtreleme) ve reduction (indirgeme) işlemlerinin yapılmasını sağlar. Şimdi bu maddelerin her birine değinelim.
Map fonksiyonu nedir?
Map fonksiyonu, geoid şeklindeki dünyanın, 2 boyutlu olan kağıda aktarılması gibi dizinin farklı bir diziye haritalanmasını (projection) sağlar. Aşağıdaki gibi Devnot’taki makaleleri barındırıran bir dizimiz olsun:
var makaleler = [
{
baslik: “Svelte Nedir? React ve Vue’nün Yerini Alabilir mi?”,
yazar: “Zafer AYAN”,
yayinTarihi: “16/09/2019″,
etiketler: ["hooks", "react", "svelte", "virtual dom","vue"],
konu: ["Programlama", "JavaScript"]
},
{
baslik: “Rust Programlama Diline Giriş”,
yazar: “Zafer AYAN”,
yayinTarihi: “09/09/2019″,
etiketler: ["rust", "mozilla"],
konu: ["Programlama", "Rust"]
},
{
baslik: “Kaos Mühendisliği (Chaos Engineering) Nedir?”,
yazar: “Zafer AYAN”,
yayinTarihi: “02/09/2019″,
etiketler: ["chaos engineering", "chaos monkey", "kaos mühendisliği"],
konu: ["DevOps"]
},
{
baslik: “Dependency Injection ve Ninject”,
yazar: “Yusuf YILMAZ”,
yayinTarihi: “26/08/2019″,
etiketler: ["dependency injection"],
konu: ["Tasarım Desenleri ve Prensipler"]
},
{
baslik: “React Native ve Hooks ile bir Haber Uygulaması Yapımı”,
yazar: “Zafer AYAN”,
yayinTarihi: “09/08/2019″,
etiketler: ["fetch api", "react hooks", "react native", "react navigation"],
konu: ["Programlama", "JavaScript"]
}
];
Her bir makalenin başlık ve yazarını tutan bir dizi üretmek isteseydik aşağıdaki şekilde olacaktı:
getMakaleBaslikVeYazar = (makaleler) => {
var tempMakaleler = [];
for (var i = 0; i < makaleler.length; i++) {
tempMakaleler.push({ baslik: makaleler[i].baslik, yazar: makaleler[i].yazar });
}
return tempMakaleler;
};
Fonksiyonu çağırdığımızda aşağıdaki gibi doğru sonucu verecektir:
Peki burada i
değişkeni ile index tanımlamaya gerek var mıydı? tempMakaleler
dizisini gerçekten tanımlamak gerekiyor muydu? Ya da makaleler’in tümü üzerinde gezinmek için length özelliği ile bir kıyaslama yapmak gerekiyor muydu? Bu gibi işleri map fonksiyonu ile çok daha kısa ve sade biçimde yapabiliriz:
getMakaleBaslikVeYazar = (makaleler) => {
return makaleler.map(x => ({ baslik: x.baslik, yazar: x.yazar }));
};
Fonksiyonu çağırdığımızda for döngüsüyle aynı sonucu verdiğini göreceksiniz. Buradaki map
fonksiyonu ile niyetimizin makaleler parametresini bir diziye haritalama yapmak olduğunu da belirtmiş olduk. Bizden sonra gelen bir geliştirici koda baktığında kolayca adapte olabilecektir. Peki ya eylül ayından sonraki makale listesini almak istersek ne olacak? map
ile sadece haritalama yapabildiğimiz için ihtiyacımızı görmeyecektir. İşte bu durumda filter
fonksiyonu imdadımıza koşuyor.
Filter fonksiyonu nedir?
Filter fonksiyonu, belirli bir boolean ifadeye göre ilgili dizi elemanlarının yeni oluşacak diziye aktarılmasını sağlar. Örneğin eylül ayı ve sonraki yazıları listelemek isteyelim. Klasik for döngüsü ile uğraşsaydık aşağıdaki gibi bir kod ortaya çıkacaktı:
// Tarih bilgisinin Date’e dönüştürülmesi için metot
toDate = (strDate) => {
var parts = strDate.split(“/”);
var date = new Date(
parseInt(parts[2], 10),
parseInt(parts[1], 10) – 1,
parseInt(parts[0], 10)
);
return date;
};
getEylulSonrasıMakaleler = (makaleler) => {
var tempMakaleler = [];
for (var i = 0; i < makaleler.length; i++) {
if (toDate(makaleler[i].yayinTarihi) >= toDate(“01/09/2019″))
tempMakaleler.push(makaleler[i]);
}
return tempMakaleler;
};
Fonksiyonun çıktısı aşağıdaki gibi olacaktır:
Bu fonksiyonu filter ile daha sade biçimde yazabiliriz:
getEylulSonrasıMakaleler = (makaleler) => {
return makaleler.filter(x => toDate(x.yayinTarihi) >= toDate(“01/09/2019″));
};
Aynı sonucu tek satırda almış olduk. Peki ya sadece makale başlığı ve yazar adını almak isteseydik? O zaman map fonksiyonunu zincirleme (chaining) bir şekilde kullanabiliriz:
getEylulSonrasıMakaleler = (makaleler) => {
return makaleler
.filter(x => toDate(x.yayinTarihi) >= toDate(“01/09/2019″))
.map(x => ({ baslik: x.baslik, yazar: x.yazar }));
};
En son yayınlanan makaleyi almak istediğimizde filter fonksiyonu da işe yaramayacaktır. Bunun için reduce
fonksiyonunu kullanabiliriz.
Reduce fonksiyonu nedir?
Reduce fonksiyonu, verilen bir koşula göre diziden tek bir eleman almak için kullanılır. En yüksek değerli elemanı alma, elemanların toplamını bulma gibi işlemlerde kullanılabilir. for
döngüsü ile en son çıkan makaleyi almaya çalışalım:
getSonMakale = (makaleler) => {
var tempEnYeniMakale = undefined;
for (var i = 0; i < makaleler.length; i++) {
if (tempEnYeniMakale === undefined) {
tempEnYeniMakale = makaleler[i];
}
else {
if (toDate(makaleler[i].yayinTarihi) >= toDate(tempEnYeniMakale.yayinTarihi)){
tempEnYeniMakale = makaleler[i];
}
}
}
return tempEnYeniMakale;
};
Bunu reduce ile yapmak istediğimizde aşağıdaki gibi bir kod yazmamız yeterlidir:
getSonMakale = (makaleler) => {
return makaleler.reduce((max, curr) =>
toDate(max.yayinTarihi) >= toDate(curr.yayinTarihi) ? max : curr);
};
reduce
fonksiyonu temelde max
ve curr
olmak üzere iki parametre alır. İlk parametrede indirgenecek sonuç değeri, diğerinde ise o anki index elemanı yer alır. Bu sayede çok kısa bir biçimde istenen eleman elde edilebilir.
ConcatAll fonksiyonu nedir?
ConcatAll fonksiyonu [[1, 2], [3, 4], [5]]
gibi iç içe dizilere uygulandığında içteki diziyi açıp birleştirmek [1, 2, 3, 4, 5]
için kullanılmaktadır. Böyle bir fonksiyon normalde JavaScript API’sinde bulunmamaktadır. Ancak reactiveX gibi popüler kütüphanelerde mevcut olduğu için buna da değinebiliriz. Örneğin aşağıdaki gibi bir makaleler dizisi olsun:
var makaleler = [
{
konusu: “programlama”,
icerikleri: [
{
baslik: “Svelte Nedir? React ve Vue’nün Yerini Alabilir mi?”,
yazar: “Zafer AYAN”,
yayinTarihi: “16/09/2019″,
etiketler: ["hooks", "react", "svelte", "virtual dom", "vue"],
konu: ["Programlama", "JavaScript"]
},
{
baslik: “Rust Programlama Diline Giriş”,
yazar: “Zafer AYAN”,
yayinTarihi: “09/09/2019″,
etiketler: ["rust", "mozilla"],
konu: ["Programlama", "Rust"]
},
{
baslik: “React Native ve Hooks ile bir Haber Uygulaması Yapımı”,
yazar: “Zafer AYAN”,
yayinTarihi: “09/08/2019″,
etiketler: ["fetch api", "react hooks", "react native", "react navigation"],
konu: ["Programlama", "JavaScript"]
}
]
},
{
konusu: “devOps”,
icerikleri: [
{
baslik: “Kaos Mühendisliği (Chaos Engineering) Nedir?”,
yazar: “Zafer AYAN”,
yayinTarihi: “02/09/2019″,
etiketler: ["chaos engineering", "chaos monkey", "kaos mühendisliği"],
konu: ["DevOps"]
}
]
},
{
konusu: “tasarım desenleri”,
icerikleri: [
{
baslik: “Dependency Injection ve Ninject”,
yazar: “Yusuf YILMAZ”,
yayinTarihi: “26/08/2019″,
etiketler: ["dependency injection"],
konu: ["Tasarım Desenleri ve Prensipler"]
}
]
}
];
Bu dizide makalenin konusu, başlığı ve yazarın adını alabilmek için aşağıdaki şekilde çalıştırmamız gerekirdi:
getMakaleler = (makaleler) => {
return makaleler.map(makale =>
makale.icerikleri.map(icerik =>
({ konu: makale.konusu, baslik: icerik.baslik, yazar: icerik.yazar })
)
);
}
Fakat bu durumda içiçe bir dizi [[makale, makale, makale], [makale], [makale]]
üreteceği için bu diziyi tek boyuta indirgememiz gerekiyor. Bunun için concatAll
fonksiyonunu oluşturalım:
Array.prototype.concatAll = () => {
var sonuc = [];
this.forEach((iceridekiDizi) => sonuc.push.apply(sonuc, iceridekiDizi));
return sonuc;
};
Fonksiyonu Array.prototype.concatAll
şeklinde ürettiğimiz için herhangi bir iki boyutlu dizi üzerinde kullanabiliriz:
[[1, 2, 3], [4], [5]].concatAll();
// << [1, 2, 3, 4, 5]
getMakaleler fonksiyonu için de aşağıdaki şekilde eklenebilir:
getMakaleler = (makaleler) => {
return makaleler.map(makale =>
makale.icerikleri.map(icerik =>
({ konu: makale.konusu, baslik: icerik.baslik, yazar: icerik.yazar })
)
).concatAll();
}
Sonuç olarak
JavaScript içerisinde hazır olarak gelen map
, filter
ve reduce
gibi fonksiyonlar hem daha az kod yazmanızı hem de for’a göre daha anlaşılır bir fonksiyon çağrımı yapmanızı sağlıyor. Eğer bu yazı hakkında soru ve görüşleriniz varsa yorum bölümünden bize yazabilirsiniz. Görüşmek üzere…