Fungsi tingkat tinggi dengan koleksi

1. Pengantar

Di codelab Menggunakan jenis fungsi dan ekspresi lambda di Kotlin, Anda telah mempelajari fungsi tingkat tinggi, yang merupakan fungsi yang menggunakan fungsi lain sebagai parameter dan/atau menampilkan fungsi, seperti repeat(). Fungsi tingkat tinggi sangat relevan dengan koleksi karena membantu Anda melakukan tugas umum, seperti mengurutkan atau memfilter, dengan lebih sedikit kode. Setelah Anda memahami dasar yang kuat untuk menggunakan koleksi, saatnya untuk meninjau kembali fungsi tingkat tinggi.

Dalam codelab ini, Anda akan mempelajari berbagai fungsi yang dapat digunakan pada jenis koleksi, termasuk forEach(), map(), filter(), groupBy(), fold(), dan sortedBy(). Dalam proses ini, Anda akan mendapatkan latihan tambahan menggunakan ekspresi lambda.

Prasyarat

  • Pemahaman tentang jenis fungsi dan ekspresi lambda.
  • Pemahaman tentang sintaksis lambda di akhir, seperti dengan fungsi repeat().
  • Pengetahuan tentang berbagai jenis koleksi di Kotlin, seperti List.

Yang akan Anda pelajari

  • Cara menyematkan ekspresi lambda ke dalam string.
  • Cara menggunakan berbagai fungsi tingkat tinggi dengan koleksi List, termasuk forEach(), map(), filter(), groupBy(), fold(), dan sortedBy().

Yang Anda butuhkan

  • Browser web dengan akses ke Kotlin Playground.

2. forSetiap() dan template string dengan lambda

Kode awal

Pada contoh berikut, Anda akan mengambil List yang merepresentasikan menu kue di toko roti (sungguh lezat!), dan menggunakan fungsi tingkat tinggi untuk memformat menu dengan cara yang berbeda.

Mulailah dengan menyiapkan kode awal.

  1. Buka Kotlin Playground.
  2. Di atas fungsi main(), tambahkan class Cookie. Setiap instance Cookie merepresentasikan item pada menu, dengan name, price, dan informasi lainnya tentang kue tersebut.
class Cookie(
    val name: String,
    val softBaked: Boolean,
    val hasFilling: Boolean,
    val price: Double
)

fun main() {

}
  1. Di bawah class Cookie, di luar main(), buat daftar kue seperti yang ditampilkan. Jenis ini disimpulkan sebagai List<Cookie>.
class Cookie(
    val name: String,
    val softBaked: Boolean,
    val hasFilling: Boolean,
    val price: Double
)

val cookies = listOf(
    Cookie(
        name = "Chocolate Chip",
        softBaked = false,
        hasFilling = false,
        price = 1.69
    ),
    Cookie(
        name = "Banana Walnut",
        softBaked = true,
        hasFilling = false,
        price = 1.49
    ),
    Cookie(
        name = "Vanilla Creme",
        softBaked = false,
        hasFilling = true,
        price = 1.59
    ),
    Cookie(
        name = "Chocolate Peanut Butter",
        softBaked = false,
        hasFilling = true,
        price = 1.49
    ),
    Cookie(
        name = "Snickerdoodle",
        softBaked = true,
        hasFilling = false,
        price = 1.39
    ),
    Cookie(
        name = "Blueberry Tart",
        softBaked = true,
        hasFilling = true,
        price = 1.79
    ),
    Cookie(
        name = "Sugar and Sprinkles",
        softBaked = false,
        hasFilling = false,
        price = 1.39
    )
)

fun main() {

}

Melakukan loop pada daftar dengan forEach()

Fungsi tingkat tinggi pertama yang Anda pelajari adalah fungsi forEach(). Fungsi forEach() menjalankan fungsi yang diteruskan sebagai parameter satu kali untuk setiap item dalam koleksi. Cara kerjanya mirip dengan fungsi repeat(), atau loop for. Lambda dieksekusi untuk elemen pertama, lalu elemen kedua, dan seterusnya, sampai dieksekusi untuk setiap elemen dalam koleksi. Tanda tangan metode adalah sebagai berikut:

forEach(action: (T) -> Unit)

forEach() mengambil satu parameter tindakan—fungsi dari jenis (T) -> Unit.

T sesuai dengan jenis data apa pun yang ada dalam koleksi. Karena lambda mengambil satu parameter, Anda dapat menghilangkan nama dan merujuk ke parameter dengan it.

Gunakan fungsi forEach() untuk mencetak item dalam daftar cookies.

  1. Di main(), panggil forEach() di daftar cookies, menggunakan sintaksis lambda di akhir. Karena lambda di akhir adalah satu-satunya argumen, Anda dapat menghilangkan tanda kurung saat memanggil fungsi.
fun main() {
    cookies.forEach {

    }
}
  1. Dalam isi lambda, tambahkan pernyataan println() yang mencetak it.
fun main() {
    cookies.forEach {
        println("Menu item: $it")
    }
}
  1. Jalankan kode Anda dan amati outputnya. Semua yang dicetak adalah nama jenis (Cookie), dan ID unik untuk objek, tetapi bukan konten objek.
Menu item: Cookie@5a10411
Menu item: Cookie@68de145
Menu item: Cookie@27fa135a
Menu item: Cookie@46f7f36a
Menu item: Cookie@421faab1
Menu item: Cookie@2b71fc7e
Menu item: Cookie@5ce65a89

Menyematkan ekspresi dalam string

Saat pertama kali diperkenalkan ke template string, Anda melihat bagaimana simbol dolar ($) dapat digunakan dengan nama variabel untuk menyisipkannya ke dalam string. Namun, cara ini tidak berfungsi seperti yang diharapkan saat dikombinasikan dengan operator titik (.) untuk mengakses properti.

  1. Pada panggilan ke forEach(), ubah isi lambda untuk menyisipkan $it.name ke dalam string.
cookies.forEach {
    println("Menu item: $it.name")
}
  1. Jalankan kode. Perhatikan bahwa kode ini menyisipkan nama class, Cookie, dan ID unik untuk objek yang diikuti dengan .name. Nilai properti name tidak diakses.
Menu item: Cookie@5a10411.name
Menu item: Cookie@68de145.name
Menu item: Cookie@27fa135a.name
Menu item: Cookie@46f7f36a.name
Menu item: Cookie@421faab1.name
Menu item: Cookie@2b71fc7e.name
Menu item: Cookie@5ce65a89.name

Untuk mengakses properti dan menyematkannya dalam string, Anda memerlukan ekspresi. Anda dapat membuat ekspresi menjadi bagian dari template string dengan mengapitnya menggunakan tanda kurung kurawal.

2c008744cee548cc.png

Ekspresi lambda ditempatkan di antara tanda kurung kurawal buka dan tutup. Anda dapat mengakses properti, melakukan operasi matematika, fungsi panggilan, dll., dan nilai hasil lambda disisipkan ke dalam string.

Mari kita ubah kode agar nama disisipkan ke dalam string.

  1. Mengapit it.name dalam tanda kurung kurawal untuk menjadikannya ekspresi lambda.
cookies.forEach {
    println("Menu item: ${it.name}")
}
  1. Jalankan kode. Output-nya berisi name dari setiap Cookie.
Menu item: Chocolate Chip
Menu item: Banana Walnut
Menu item: Vanilla Creme
Menu item: Chocolate Peanut Butter
Menu item: Snickerdoodle
Menu item: Blueberry Tart
Menu item: Sugar and Sprinkles

3. map()

Fungsi map() memungkinkan Anda mengubah koleksi menjadi koleksi baru dengan jumlah elemen yang sama. Misalnya, map() dapat mengubah List<Cookie> menjadi List<String> yang hanya berisi name kue, asalkan Anda memberi tahu fungsi map() cara membuat String dari setiap item Cookie.

e0605b7b09f91717.png

Misalkan Anda menulis aplikasi yang menampilkan menu interaktif untuk toko roti. Saat pengguna membuka layar yang menampilkan menu kue, mereka mungkin ingin melihat data yang ditampilkan secara logis, seperti nama yang diikuti dengan harga. Anda dapat membuat daftar string, yang diformat dengan data yang relevan (nama dan harga), menggunakan fungsi map().

  1. Hapus semua kode sebelumnya dari main(). Buat variabel baru bernama fullMenu, dan tetapkan sama dengan hasil pemanggilan map() di daftar cookies.
val fullMenu = cookies.map {

}
  1. Dalam isi lambda, tambahkan string yang diformat untuk menyertakan name dan price dari it.
val fullMenu = cookies.map {
    "${it.name} - $${it.price}"
}
  1. Cetak konten fullMenu. Anda dapat melakukannya menggunakan forEach(). Koleksi fullMenu yang ditampilkan dari map() memiliki jenis List<String>, bukan List<Cookie>. Setiap Cookie di cookies sesuai dengan String di fullMenu.
println("Full menu:")
fullMenu.forEach {
    println(it)
}
  1. Jalankan kode. Outputnya akan cocok dengan konten daftar fullMenu.
Full menu:
Chocolate Chip - $1.69
Banana Walnut - $1.49
Vanilla Creme - $1.59
Chocolate Peanut Butter - $1.49
Snickerdoodle - $1.39
Blueberry Tart - $1.79
Sugar and Sprinkles - $1.39

4. filter()

Fungsi filter() memungkinkan Anda membuat subset koleksi. Misalnya, jika memiliki daftar angka, Anda dapat menggunakan filter() untuk membuat daftar baru yang hanya berisi angka yang habis dibagi 2.

d4fd6be7bef37ab3.png

Sementara hasil dari fungsi map() selalu menghasilkan koleksi dengan ukuran yang sama, filter() menghasilkan koleksi dengan ukuran yang sama atau lebih kecil dari koleksi asli. Tidak seperti map(), koleksi yang dihasilkan juga memiliki jenis data yang sama, sehingga memfilter List<Cookie> akan menghasilkan List<Cookie> lain.

Seperti map() dan forEach(), filter() menggunakan ekspresi lambda tunggal sebagai parameter. Lambda memiliki parameter tunggal yang merepresentasikan setiap item dalam koleksi dan menampilkan nilai Boolean.

Untuk setiap item dalam koleksi:

  • Jika hasil ekspresi lambda adalah true, item tersebut akan disertakan dalam koleksi baru.
  • Jika hasilnya adalah false, item tidak disertakan dalam koleksi baru.

Hal ini berguna jika Anda ingin mendapatkan subset data di aplikasi Anda. Misalnya, toko roti tersebut ingin lebih menampilkan kue yang dipanggang lembut di bagian menu yang terpisah. Anda dapat filter() daftar cookies terlebih dahulu sebelum mencetak item.

  1. Di main(), buat variabel baru bernama softBakedMenu, dan tetapkan ke hasil pemanggilan filter() di daftar cookies.
val softBakedMenu = cookies.filter {
}
  1. Di bagian isi lambda, tambahkan ekspresi boolean untuk memeriksa apakah properti softBaked kue sama dengan true. Karena softBaked adalah Boolean itu sendiri, isi lambda hanya perlu berisi it.softBaked.
val softBakedMenu = cookies.filter {
    it.softBaked
}
  1. Cetak konten softBakedMenu menggunakan forEach().
println("Soft cookies:")
softBakedMenu.forEach {
    println("${it.name} - $${it.price}")
}
  1. Jalankan kode. Menu dicetak seperti sebelumnya, tetapi hanya mencakup kue yang lembut.
...
Soft cookies:
Banana Walnut - $1.49
Snickerdoodle - $1.39
Blueberry Tart - $1.79

5. groupBy()

Fungsi groupBy() dapat digunakan untuk mengubah daftar menjadi peta, berdasarkan fungsi. Setiap nilai unik yang ditampilkan dari fungsi tersebut akan menjadi kunci di peta yang dihasilkan. Nilai untuk setiap kunci adalah semua item dalam koleksi yang menghasilkan nilai unik yang ditampilkan tersebut.

54e190b34d9921c0.png

Jenis data kunci sama dengan jenis nilai yang ditampilkan dari fungsi yang diteruskan ke groupBy(). Jenis data nilai adalah daftar item dari daftar asli.

Hal ini mungkin sulit untuk dikonseptualisasikan, jadi mari kita mulai dengan contoh sederhana. Dengan daftar angka yang sama seperti sebelumnya, kelompokkan angka tersebut sebagai ganjil atau genap.

Anda dapat memeriksa apakah angkanya ganjil atau genap dengan cara membaginya dengan 2 dan memeriksa apakah sisanya 0 atau 1. Jika sisanya 0, angka itu genap. Namun, jika sisanya 1, angka itu ganjil.

Hal ini dapat dilakukan dengan operator modulo (%). Operator modulo membagi dividen di sisi kiri ekspresi dengan pembagi di sebelah kanan.

4c3333da9e5ee352.png

Operator modulo akan menampilkan sisanya, bukan menampilkan hasil pembagian, seperti operator pembagian (/). Hal ini berguna untuk memeriksa apakah suatu angka genap atau ganjil.

4219eacdaca33f1d.png

Fungsi groupBy() dipanggil dengan ekspresi lambda berikut: { it % 2 }.

Peta yang dihasilkan memiliki dua kunci: 0 dan 1. Setiap kunci memiliki nilai jenis List<Int>. Daftar untuk kunci 0 berisi semua bilangan genap, sedangkan daftar untuk kunci 1 berisi semua bilangan ganjil.

Kasus penggunaan di dunia nyata adalah aplikasi foto yang mengelompokkan foto berdasarkan subjek atau lokasi tempat foto diambil. Untuk menu toko roti, mari kita kelompokkan menu berdasarkan apakah kue dipanggang lembut atau tidak.

Gunakan groupBy() untuk mengelompokkan menu berdasarkan properti softBaked.

  1. Hapus panggilan ke filter() dari langkah sebelumnya.

Kode yang akan dihapus

val softBakedMenu = cookies.filter {
    it.softBaked
}
println("Soft cookies:")
softBakedMenu.forEach {
    println("${it.name} - $${it.price}")
}
  1. Panggil groupBy() di daftar cookies untuk menyimpan hasil dalam variabel bernama groupedMenu.
val groupedMenu = cookies.groupBy {}
  1. Teruskan ekspresi lambda yang menampilkan it.softBaked. Jenis nilai yang ditampilkan akan menjadi Map<Boolean, List<Cookie>>.
val groupedMenu = cookies.groupBy { it.softBaked }
  1. Buat variabel softBakedMenu yang berisi nilai groupedMenu[true], dan variabel crunchyMenu yang berisi nilai groupedMenu[false]. Karena hasil dari subskrip Map adalah nullable, Anda dapat menggunakan operator Elvis (?:) untuk menampilkan daftar kosong.
val softBakedMenu = groupedMenu[true] ?: listOf()
val crunchyMenu = groupedMenu[false] ?: listOf()
  1. Tambahkan kode untuk mencetak menu kue yang lembut, diikuti dengan menu untuk kue yang renyah.
println("Soft cookies:")
softBakedMenu.forEach {
    println("${it.name} - $${it.price}")
}
println("Crunchy cookies:")
crunchyMenu.forEach {
    println("${it.name} - $${it.price}")
}
  1. Jalankan kode. Dengan menggunakan fungsi groupBy(), Anda membagi daftar menjadi dua, berdasarkan nilai salah satu properti.
...
Soft cookies:
Banana Walnut - $1.49
Snickerdoodle - $1.39
Blueberry Tart - $1.79
Crunchy cookies:
Chocolate Chip - $1.69
Vanilla Creme - $1.59
Chocolate Peanut Butter - $1.49
Sugar and Sprinkles - $1.39

6. fold()

Fungsi fold() digunakan untuk menghasilkan nilai tunggal dari koleksi. Fungsi ini paling sering digunakan untuk hal-hal seperti menghitung total harga, atau menjumlahkan semua elemen dalam daftar untuk menemukan rata-rata.

a9e11a1aad05cb2f.png

Fungsi fold() mengambil dua parameter:

  • Nilai awal. Jenis data disimpulkan saat memanggil fungsi (yaitu nilai awal 0 disimpulkan sebagai Int).
  • Ekspresi lambda yang menampilkan nilai berjenis sama dengan nilai awal.

Ekspresi lambda juga memiliki dua parameter:

  • Yang pertama dikenal sebagai akumulator. Akumulator memiliki jenis data yang sama dengan nilai awal. Anggap saja ini sebagai total run. Setiap kali ekspresi lambda dipanggil, akumulator sama dengan nilai yang ditampilkan dari waktu sebelumnya lambda dipanggil.
  • Yang kedua adalah jenis yang sama dengan setiap elemen dalam koleksi.

Seperti fungsi lain yang telah Anda lihat, ekspresi lambda dipanggil untuk setiap elemen dalam koleksi, sehingga Anda dapat menggunakan fold() sebagai cara ringkas untuk menjumlahkan semua elemen.

Mari kita gunakan fold() untuk menghitung harga total semua kue.

  1. Di main(), buat variabel baru bernama totalPrice dan tetapkan sama dengan hasil pemanggilan fold() di daftar cookies. Teruskan 0.0 untuk nilai awal. Jenisnya disimpulkan sebagai Double.
val totalPrice = cookies.fold(0.0) {
}
  1. Anda harus menentukan kedua parameter untuk ekspresi lambda. Gunakan total untuk akumulator, dan cookie untuk elemen koleksi. Gunakan tanda panah (->) setelah daftar parameter.
val totalPrice = cookies.fold(0.0) {total, cookie ->
}
  1. Dalam isi lambda, hitung jumlah total dan cookie.price. Ini disimpulkan sebagai nilai yang ditampilkan dan diteruskan untuk total saat lambda dipanggil lagi.
val totalPrice = cookies.fold(0.0) {total, cookie ->
    total + cookie.price
}
  1. Cetak nilai totalPrice, yang diformat sebagai string untuk keterbacaan.
println("Total price: $${totalPrice}")
  1. Jalankan kode. Hasilnya harus sama dengan jumlah harga di daftar cookies.
...
Total price: $10.83

7. sortedBy()

Saat pertama kali mempelajari koleksi, Anda mengetahui bahwa fungsi sort() dapat digunakan untuk mengurutkan elemen. Namun, tindakan ini tidak akan berfungsi pada koleksi objek Cookie. Class Cookie memiliki beberapa properti dan Kotlin tidak akan mengetahui properti mana (name, price, dll.) yang ingin Anda urutkan.

Untuk kasus ini, koleksi Kotlin menyediakan fungsi sortedBy(). sortedBy() memungkinkan Anda menentukan lambda yang menampilkan properti yang ingin Anda urutkan. Misalnya, jika Anda ingin mengurutkan berdasarkan price, lambda akan menampilkan it.price. Selama jenis data nilai memiliki tata urutan alami—string diurutkan menurut abjad, nilai numerik diurutkan dalam urutan menaik—string akan diurutkan seperti koleksi jenis tersebut.

5fce4a067d372880.png

Anda akan menggunakan sortedBy() untuk mengurutkan daftar kue menurut abjad.

  1. Di main(), setelah kode yang ada, tambahkan variabel baru bernama alphabeticalMenu dan tetapkan agar sama dengan memanggil sortedBy() di daftar cookies.
val alphabeticalMenu = cookies.sortedBy {
}
  1. Dalam ekspresi lambda, tampilkan it.name. Daftar yang dihasilkan akan tetap berjenis List<Cookie>, tetapi diurutkan berdasarkan name.
val alphabeticalMenu = cookies.sortedBy {
    it.name
}
  1. Cetak nama kue dalam alphabeticalMenu. Anda dapat menggunakan forEach() untuk mencetak setiap nama pada baris baru.
println("Alphabetical menu:")
alphabeticalMenu.forEach {
    println(it.name)
}
  1. Jalankan kode. Nama kue dicetak dalam urutan abjad.
...
Alphabetical menu:
Banana Walnut
Blueberry Tart
Chocolate Chip
Chocolate Peanut Butter
Snickerdoodle
Sugar and Sprinkles
Vanilla Creme

8. Kesimpulan

Selamat! Anda baru saja melihat beberapa contoh bagaimana fungsi tingkat tinggi dapat digunakan dengan koleksi. Operasi umum, seperti pengurutan dan pemfilteran, dapat dilakukan dalam satu baris kode, membuat program Anda lebih ringkas dan ekspresif.

Ringkasan

  • Anda dapat melakukan loop pada setiap elemen dalam koleksi menggunakan forEach().
  • Ekspresi dapat disisipkan ke dalam string.
  • map() digunakan untuk memformat item dalam koleksi, sering kali sebagai koleksi jenis data lain.
  • filter() dapat membuat subset koleksi.
  • groupBy() membagi koleksi berdasarkan nilai fungsi yang ditampilkan.
  • fold() mengubah koleksi menjadi satu nilai.
  • sortedBy() digunakan untuk mengurutkan koleksi berdasarkan properti tertentu.

9. Pelajari lebih lanjut