---
title: 'Dönüşüm İşlemleri'
source: 'https://academia.sh/tr/kurslar/kotlin/donusum-islemleri'
course: 'Kotlin Dili'
language: tr
updated: '2026-10-21T05:02:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'map, filter, fold ve reduce işlemlerinin sonucunu ve boş koleksiyondaki farkı ölçer'
    - 'groupBy, associate ve zip işlemlerinin ürettiği koleksiyonu çıktıdan okur'
    - 'Aynı dönüşümün iki kez uygulanmasının eşit ama ayrı koleksiyon verdiğini gösterir'
    - 'distinct ve toSet işlemlerinin elemanın kendi eşitliğine dayandığını ölçer'
---

# Dönüşüm İşlemleri

Eşleme, süzme, katlama ve gruplamanın yeni koleksiyon ürettiğinin, aynı dönüşümün iki kez uygulanınca eşit ama ayrı sonuç verdiğinin ve distinct ile toSet'in elemanın eşitliğine dayandığının ölçümü.

Önceki ders koleksiyonları bir veri kabı olarak ölçtü ve içeriğin eşitliği belirlediğini gösterdi. Gerçek programlarda koleksiyon nadiren olduğu gibi kullanılır. Sipariş listesinden tutarları toplamak, bir şehre ait siparişleri ayırmak, iki listeyi eşleştirmek gerekir. Bunların hepsi **dönüşüm işlemleridir**: bir koleksiyonu alır, ondan yeni bir koleksiyon ya da tek bir değer üretirler. Dönüşüm işlemlerinin çoğu **yüksek mertebeden fonksiyon**dur, yani parametre olarak bir lambda alır; `sorted()` ya da `distinct()` gibi lambda almayanlar da vardır. Lambda, her elemana uygulanacak kuralı taşır; işlemin adı bu kuralın ne için kullanılacağını söyler.

Dört temel işlemin Türkçe adları şunlardır. `map` **eşleme**dir: her elemandan bir yenisini üretir ve sonuç aynı uzunlukta bir listedir. `filter` **süzme**dir: lambdanın doğru dediği elemanları tutar. `fold` **katlama**dır: bir başlangıç değerinden yola çıkıp elemanları tek bir değere birleştirir. `reduce` **indirgeme**dir: katlamanın başlangıç değeri vermeyen biçimidir, ilk elemanı başlangıç sayar. Kursun ortak ölçü sorusu bu derste de geri döner: **bu iki değeri eşit yapan ne?** Dört ölçüt (tekil, kimlik, içerik, kısmi içerik) önceki derste tanıtıldığı biçimde kalır. `Criterion` sınıfı olduğu gibi tekrar verilir.

```kotlin
// Criterion.kt — iki degeri esit yapan olcutu siniflandirir
class Criterion(val label: String, first: Any?, twin: Any?, other: Any?) {
    val sameObject = first === twin
    val equalTwin = first == twin
    val equalOther = first == other

    val name: String = when {
        sameObject -> "tekil"
        !equalTwin -> "kimlik"
        !equalOther -> "icerik"
        else -> "kismi-icerik"
    }

    fun report() {
        println("$label: ayni-nesne=$sameObject, ikiz=$equalTwin, farkli=$equalOther -> $name")
    }
}

fun summarize(checks: List<Criterion>) {
    for (name in listOf("tekil", "kimlik", "icerik", "kismi-icerik")) {
        println("$name: ${checks.count { it.name == name }}")
    }
}
```

Bu dersin ölçümleri dört ayrı veri tipi kullanır. `Order` bir sipariş satırıdır: kimlik, şehir ve tutar. `Ticket` yalnız bir kimlik taşıyan **veri sınıfı**dır; `Plain` aynı alanı taşıyan ama veri sınıfı olmayan sıradan bir sınıftır. `Session` ise kimliği kurucuda, ziyaret sayısını gövdede taşıyan bir veri sınıfıdır. Veri sınıfı `equals`'ı kurucudaki özelliklerden üretir; gövdede tanımlanan özellik bu üretime girmez. `session` fonksiyonu iki alanı birlikte kuran küçük bir yardımcıdır. Bu bloğun da çıktısı yoktur.

```kotlin
// Models.kt — donusum olcumlerinin kullandigi tipler
data class Order(val id: Int, val city: String, val amount: Int)

data class Ticket(val id: Int)

class Plain(val id: Int)

data class Session(val id: Int) {
    var visits = 0
}

fun session(id: Int, visits: Int): Session {
    val result = Session(id)
    result.visits = visits
    return result
}
```

## Dört Temel İşlem

İlk ölçüm bir `Int` listesine dört işlemi uygular. `map { it * 2 }` her tutarı ikiyle çarpar, `filter { it > 6 }` altı ve altındakileri eler. `fold(0)` toplamı verir; başlangıç değeri `0` olduğu için sonuç tipi başlangıç değerinin tipidir. `reduce` ile en büyük eleman bulunur. Son iki ölçüm iki işlemin boş koleksiyonda ayrıldığı yeri gösterir: `fold` başlangıç değerini geri verir, `reduce`'un ise başlayacak ilk elemanı olmadığı için bir istisna fırlatması beklenir. Ölçümde istisna `try` ile yakalanır ve yalnız sınıf adı basılır.

```kotlin
// Main.kt — eslem, suzme, katlama, indirgeme
fun main() {
    val amounts = listOf(5, 12, 7, 20)
    println(amounts.map { it * 2 })
    println(amounts.filter { it > 6 })
    println(amounts.fold(0) { acc, n -> acc + n })
    println(amounts.reduce { acc, n -> maxOf(acc, n) })
    println(emptyList<Int>().fold(0) { acc, n -> acc + n })
    try {
        emptyList<Int>().reduce { acc, n -> acc + n }
    } catch (e: UnsupportedOperationException) {
        println(e::class.simpleName)
    }
}
```

```
[10, 24, 14, 40]
[12, 7, 20]
44
20
0
UnsupportedOperationException
```

İlk satırda dört eleman dört elemana eşlendi. İkinci satırda yalnız üç eleman süzmeden geçti: `5` elendi. Üçüncü satır `5 + 12 + 7 + 20` toplamıdır; dördüncü satır `maxOf` ile `20`'yi bulur. Beşinci satır `fold`'un boş listede başlangıç değeri olan `0`'ı döndürdüğünü, altıncı satır `reduce`'un aynı durumda `UnsupportedOperationException` fırlattığını gösterir. Kural açıktır: eleman olup olmadığı bilinmiyorsa `fold` seçilir; boş koleksiyon hata sayılacaksa `reduce` seçilir.

**KE7.** Bu ölçümler `Int` listeleri için. Elemanları başka tipte olan bir `reduce`'un sonuç tipinin elemanların ortak üst tipine nasıl daraldığı ve `reduceOrNull` gibi kardeş işlemler ayrıca ölçülmedi.

## Gruplama, Eşlem Kurma ve Eşleştirme

Üç işlem daha koleksiyondan koleksiyon üretir, ama sonuç liste olmak zorunda değildir. `groupBy` her elemanı bir anahtara göre toplar ve anahtardan listeye giden bir eşlem kurar. `associate` her elemandan bir anahtar-değer çifti üretir ve bunlardan bir eşlem kurar; `associateBy` anahtarı elemandan alır, değer olarak elemanın kendisini saklar. İki elemanın anahtarı aynı çıkarsa **sonradan gelen öncekinin yerini alır**. `zip` iki listeyi sırayla çiftler ve çift sayısı kısa olan listeyle sınırlıdır. Ölçümde `associateBy` sonucunun `ankara` anahtarındaki siparişin kimliği okunur; iki ankara siparişinden hangisinin kaldığı böylece görünür.

```kotlin
// Main.kt — gruplama, eslem kurma, zip
fun main() {
    val orders = listOf(Order(1, "ankara", 30), Order(2, "izmir", 10), Order(3, "ankara", 20))
    println(orders.groupBy { it.city }.mapValues { entry -> entry.value.map { it.id } })
    println(orders.associate { it.id to it.amount })
    println(orders.associateBy { it.city }["ankara"]?.id)
    println(orders.map { it.id }.zip(listOf("a", "b", "c")))
    println(listOf(1, 2, 3).zip(listOf("a", "b")))
}
```

```
{ankara=[1, 3], izmir=[2]}
{1=30, 2=10, 3=20}
3
[(1, a), (2, b), (3, c)]
[(1, a), (2, b)]
```

İlk satırda `groupBy` iki ankara siparişini tek listede topladı ve `izmir` için tek elemanlı bir liste bıraktı. İkinci satır kimlikten tutara giden bir eşlemdir. Üçüncü satırda `ankara` anahtarı için `3` okunur: kimliği `1` olan sipariş, kimliği `3` olanın gelmesiyle yerini kaybetti. Dördüncü satır üç çiftli bir listedir. Beşinci satırda ikinci liste kısa olduğu için `3` elemanı çiftlenmeden kaldı ve sonuç iki çifttir. `zip` hiçbir zaman istisna fırlatmaz; uzunlukların uyuşmaması sessizce en kısa uzunluğa indirgenir. Bu davranış çoğu zaman istenendir ama iki listenin aynı uzunlukta olması gerekiyorsa bunu `zip`'ten önce kontrol etmek gerekir.

**KE8.** Bu ölçümler üç elemanlı sipariş listesi için. `groupBy` ile `groupingBy` arasındaki farklar, `unzip` ve üç listeli `zip` ayrıca ölçülmedi.

## Ölçüt: İçerik

Dönüşüm işleminin sonucu yeni bir koleksiyondur. Aynı işlemi aynı koleksiyona iki kez uyguladığımızda iki sonuç elde ederiz. Bu iki sonucun aynı nesne olması gerekmez, ama içeriklerinin eşit olması gerekir. Aşağıdaki blok `map` ve `filter` için iki kez dönüşüm uygular. `first` ve `twin` aynı tarifle ayrı kurulur; `other` ise bir bakımdan farklıdır: `map` için son eleman `41`'dir, `filter` için bir eleman eksiktir. Son satır, her elemanı olduğu gibi döndüren `map { it }` sonucunun kaynak listeyle aynı nesne olup olmadığını sorar.

```kotlin
// Main.kt — ayni donusum iki kez
fun main() {
    val amounts = listOf(5, 12, 7, 20)
    val checks = listOf(
        Criterion("map", amounts.map { it * 2 }, amounts.map { it * 2 }, listOf(10, 24, 14, 41)),
        Criterion("filter", amounts.filter { it > 6 }, amounts.filter { it > 6 }, listOf(12, 7)),
    )
    for (check in checks) check.report()
    summarize(checks)
    println(amounts.map { it } === amounts)
}
```

```
map: ayni-nesne=false, ikiz=true, farkli=false -> icerik
filter: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 0
icerik: 2
kismi-icerik: 0
false
```

Her iki ölçüm de **içerik** çıktı. İkizler ayrı nesnelerdir (`ayni-nesne=false`), ama eşittir; farklı olan liste ise eşit değildir. Dönüşüm işleminin sonucunu tanımlayan şey, hangi çağrıdan çıktığı değil, taşıdığı elemanlardır. Bu dersin ölçütü budur: **içerik**. Son satır önemli bir ayrıntıyı ayırır: elemanları değiştirmeyen `map { it }` bile kaynak listeyi geri vermez, yeni bir liste kurar. Dönüşüm işlemleri kaynak koleksiyona dokunmaz; bu dersin ölçümlerinde sonuç her seferinde ayrı bir nesneydi ve kaynağın sonradan değişmesi sonucu etkilemedi. Boş sonuçlarda standart kitaplık ortak bir boş liste döndürebilir; bu durum burada ölçülmedi.

**KE9.** Bu ölçümler `Int` elemanlı listeler için. Boş koleksiyona uygulanan dönüşümün, önceki derste ölçülen tekil boş listeyi döndürüp döndürmediği ayrıca ölçülmedi.

## Sınırlayıcı Ölçüm

İçerik ölçütü, elemanlar `Int` iken kolay işler: iki `5` birbirine eşittir. Elemanlar kendi eşitliğini taşıyan nesneler olunca durum değişir. `distinct()` ve `toSet()` tekrar eden elemanı elemek için **elemanın kendi eşitliğini** kullanır; dönüşüm işlemi ölçütü kendisi üretmez, elemandan alır. Aşağıdaki blok aynı kuralı üç tipe uygular. `Ticket` veri sınıfıdır, iki `Ticket(1)` eşittir. `Plain` sıradan bir sınıftır, iki `Plain(1)` aynı nesne olmadıkça eşit değildir. `Session` veri sınıfıdır ama `visits` özelliği gövdede tanımlıdır ve eşitliğe girmez. `distinctBy` ise elemanı değil seçicinin döndürdüğü değeri karşılaştırır.

```kotlin
// Main.kt — distinct ve toSet ogenin olcutunu kullanir
fun main() {
    val tickets = listOf(Ticket(1), Ticket(1), Ticket(2))
    val plains = listOf(Plain(1), Plain(1), Plain(2))
    println(tickets.distinct().size)
    println(plains.distinct().size)
    println(tickets.toSet().size)
    println(plains.toSet().size)
    println(plains.distinctBy { it.id }.size)
    val sessions = listOf(session(1, 0), session(1, 5))
    println(sessions.distinct().size)
    val checks = listOf(
        Criterion("veri-sinifi", Ticket(1), Ticket(1), Ticket(2)),
        Criterion("siradan-sinif", Plain(1), Plain(1), Plain(2)),
        Criterion("govde-ozelligi", session(1, 0), session(1, 0), session(1, 5)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
2
3
2
3
2
1
veri-sinifi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
siradan-sinif: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
govde-ozelligi: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
tekil: 0
kimlik: 1
icerik: 1
kismi-icerik: 1
```

Sınırlayıcı ölçüm şudur: **`distinct` ve `toSet` ölçütü elemandan alır.** İlk dört satırda aynı kimlik dizisi (`1`, `1`, `2`) iki tipe uygulanmıştır. Veri sınıfı olan `Ticket` için `distinct` ve `toSet` iki elemana indirir: iki `Ticket(1)` eşit sayılır ve biri elenir. Sıradan sınıf olan `Plain` için ikisi de üç eleman bırakır: `Plain(1)` ile `Plain(1)` ayrı nesnelerdir ve eşit değildir, bu yüzden hiçbiri elenmez. Beşinci satırda `distinctBy { it.id }` aynı `Plain` listesini ikiye indirir. Burada ölçütü elemanın kendisi değil, seçicinin döndürdüğü `Int` belirler; elemanın sıradan sınıf olması fark etmez. Altıncı satır `Session` için bir şaşırtma gösterir: ziyaret sayıları `0` ve `5` olmasına rağmen iki oturum tek elemana iner, çünkü `visits` kurucuda olmadığı için veri sınıfının eşitliğine girmez.

Sonraki üç satır aynı üç durumu `Criterion` ile adlandırır. `Ticket` için ölçüt **içerik**tir. `Plain` için ölçüt **kimlik**tir: ikiz bile eşit değildir. `Session` için ölçüt **kısmi içerik**tir: `other` yalnız `visits` bakımından farklıdır ve yine de eşit çıkar. Dağılım 0/1/1/1 olarak üç ölçüte bölünür. Dersin ölçütü yine içeriktir, ama sınır şuradadır: dönüşüm işlemleri bir eşitlik tanımı getirmez, elemanın eşitliğini kullanır. Eleman eşitliği yanlış kurulduysa, sonuçtaki tekrar eleme de yanlış kurulmuş olur.

**KE10.** Bu ölçümler birer alanlı `Ticket` ve `Plain` ile iki alanlı `Session` için. `distinct`'in hangi tekrar eden elemanı tuttuğu (ilk mi, son mu) bu ölçümlerde sorulmadı; `distinctBy` için de aynı.

## Sıralama ve Yerinde Değişiklik

Dönüşüm işlemlerinde sık yapılan bir hata, `sorted` ile `sort` arasındaki farkı gözden kaçırmaktır. `sorted()` ve `sortedBy` yeni bir sıralı liste döndürür, kaynağa dokunmaz. `sort()` ise yalnız değiştirilebilir listede vardır, listeyi yerinde değiştirir ve bir şey döndürmez. Birincisini yazıp sonucu kullanmamak, sıralanmış bir liste beklerken eski listeyle kalmak demektir. Ayrıca `sortedBy` kararlıdır: anahtarı eşit çıkan elemanlar kaynaktaki sıralarını korur. Aşağıdaki blok bunları sırayla ölçer. Sipariş listesi şehre göre sıralanır ve yalnız kimlikler basılır; iki ankara siparişi arasındaki sıra kararlılığı buradan okunur.

```kotlin
// Main.kt — sorted yeni liste, sort yerinde
fun main() {
    val numbers = mutableListOf(3, 1, 2)
    val ordered = numbers.sorted()
    println(numbers)
    println(ordered)
    println(ordered === numbers)
    numbers.sort()
    println(numbers)
    val orders = listOf(Order(1, "izmir", 10), Order(2, "ankara", 30), Order(3, "ankara", 20), Order(4, "bursa", 5))
    println(orders.sortedBy { it.city }.map { it.id })
    println(orders.sortedByDescending { it.amount }.map { it.id })
}
```

```
[3, 1, 2]
[1, 2, 3]
false
[1, 2, 3]
[2, 3, 4, 1]
[2, 3, 1, 4]
```

Birinci satır `sorted()` çağrısından sonra `numbers`'ın dokunulmadan kaldığını, ikinci satır `ordered`'ın sıralı yeni liste olduğunu, üçüncü satır ikisinin ayrı nesneler olduğunu gösterir. `sort()` çağrısından sonra `numbers`'ın kendisi sıralanır. Beşinci satırda şehre göre sıralama `ankara`, `bursa`, `izmir` düzenini kurar ve iki ankara siparişi kaynaktaki sıralarıyla (`2`, sonra `3`) kalır; kararlılık budur. Altıncı satır tutara göre azalan sıralamadır.

**KE11.** Bu ölçümler `Int` ve `Order` listeleri için. `sortedWith` ile bileşik karşılaştırıcı, `Comparable` olmayan elemanlarda sıralama ve çok büyük listelerde sıralamanın bedeli ölçülmedi.

## Boş Koleksiyon ve Sınır Durumları

Dönüşüm işlemlerinin ikinci hata kaynağı sınırdır: koleksiyon boşsa ya da istenen parça koleksiyondan büyükse ne olur? `first()` boş listede istisna fırlatır, `firstOrNull()` ise `null` döndürür. `maxOrNull()` boş listede `null` verir. `average()` boş listede `NaN` verir, istisna fırlatmaz, bu yüzden sonuç sessizce sayı olmayan bir değer olur ve sonraki hesapları bozar. `chunked(n)` listeyi en çok `n` elemanlı parçalara böler ve son parça kısa kalabilir. `windowed(n)` ise her konumda `n` elemanlı kayan bir pencere kurar; koleksiyon `n`'den kısaysa hiç pencere üretmez. Ölçümde istisna `try` ile yakalanır ve yalnız sınıf adı basılır.

```kotlin
// Main.kt — bos koleksiyon ve parcalama
fun main() {
    val empty = emptyList<Int>()
    println(empty.firstOrNull())
    println(empty.maxOrNull())
    println(empty.average())
    try {
        empty.first()
    } catch (e: NoSuchElementException) {
        println(e::class.simpleName)
    }
    val five = listOf(1, 2, 3, 4, 5)
    println(five.chunked(2))
    println(five.windowed(3))
    println(listOf(1, 2).windowed(3))
    println(listOf("1", "x", "3").mapNotNull { it.toIntOrNull() })
}
```

```
null
null
NaN
NoSuchElementException
[[1, 2], [3, 4], [5]]
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]
[]
[1, 3]
```

İlk iki satır `null` verir. Üçüncü satırda boş listenin ortalaması `NaN` çıktı: istisna yok, ama sonucu sayı sanıp kullanan kod yanlış çalışır. Bu yüzden ortalama almadan önce listenin boş olup olmadığını denetlemek ya da `isEmpty()` sonucuna göre dallanmak gerekir. Dördüncü satır `first()`'in boş listede `NoSuchElementException` fırlattığını gösterir. Beşinci satırda `chunked(2)` beş elemanı `[1, 2]`, `[3, 4]`, `[5]` olarak üç parçaya böler; son parça kısadır. Altıncı satırda `windowed(3)` üç pencere üretir: `[1, 2, 3]`, `[2, 3, 4]`, `[3, 4, 5]`. Yedinci satırda iki elemanlı liste için pencere kurulamaz ve sonuç boş listedir. Son satırda `mapNotNull`, dönüştürme sırasında `null` veren elemanı atar: `"x"` sayıya çevrilemediği için yalnız `1` ve `3` kalır. `map` ve `filterNotNull` ikilisinin tek adımda yazılmış biçimidir.

**KE12.** Bu ölçümler `Int` ve `String` listeleri için. `windowed`'un `step` ve `partialWindows` parametreleri ve `Double` listelerinde `average` dışındaki toplayıcı işlemlerin boş durumları ölçülmedi.

## Özet

- `map`, `filter`, `fold` ve `reduce` kaynak koleksiyona dokunmaz; bu dersin ölçümlerinde sonuç ayrı bir nesneydi. `fold` boş koleksiyonda başlangıç değerini, `reduce` bir istisna verir.
- `groupBy` anahtardan listeye giden, `associate` ve `associateBy` anahtardan değere giden bir eşlem kurar; anahtar çakışınca sonraki girdi öncekinin yerini alır. `zip` kısa olan listeye göre keser.
- Aynı dönüşümü aynı koleksiyona iki kez uygulamak eşit ama ayrı iki koleksiyon verir. Bu dersin ölçütü: **içerik**.
- Sınırlayıcı ölçüm: `distinct` ve `toSet` elemanın kendi eşitliğine dayanır. Veri sınıfı öğelerde tekrarlar elenir, sıradan sınıf öğelerinde elenmez, gövde özelliği eşitliğe girmez; `distinctBy` ölçütü seçicinin değerinden alır.

## Sonraki Adım

Bu ders her dönüşüm işleminin hemen çalıştığını ve her adımda yeni bir koleksiyon kurduğunu varsaydı. Zincirdeki ara listeler gereksizse ya da kaynak sonsuzsa bu varsayım gereksiz iş yaptırır ya da hiç bitmez. Bir sonraki ders aynı işlemleri tembel dizilerle (`Sequence`) çalıştırır ve eşitlik sorusunu orada yeniden sorar: iki tembel dizi, aynı elemanları üretiyor olsa da eşit midir?
