Tập hợp trong Kotlin

1. Trước khi bắt đầu

Trong lớp học lập trình này, bạn sẽ tìm hiểu thêm về tập hợp, lambda và các hàm bậc cao trong Kotlin.

Điều kiện tiên quyết

  • Kiến thức cơ bản về các khái niệm Kotlin như được trình bày trong các lớp học lập trình trước đây.
  • Quen thuộc với công cụ Kotlin Playground để tạo và chỉnh sửa các chương trình Kotlin.

Kiến thức bạn sẽ học được

  • Cách làm việc với các tập hợp, bao gồm tập hợp set và tập hợp map
  • Khái niệm cơ bản về lambda
  • Kiến thức cơ bản về các hàm bậc cao

Bạn cần có

2. Tìm hiểu về tập hợp

Tập hợp là một nhóm các thành phần liên quan, chẳng hạn như một danh sách từ hoặc một tập bản ghi nhân viên. Tập hợp có thể chứa các phần tử theo thứ tự hoặc không theo thứ tự và các phần tử này có thể duy nhất hoặc không. Thực sự, bạn đã tìm hiểu về một kiểu tập hợp trước đây, đó là danh sách. Danh sách chứa các phần tử theo thứ tự nhưng các phần tử này không nhất thiết phải duy nhất.

Cũng như danh sách, Kotlin có sự phân biệt giữa tập hợp thay đổi được (mutable) và không thay đổi được (immutable) kích thước. Kotlin cung cấp rất nhiều hàm để thêm hoặc xoá các phần tử cũng như xem và thao tác trên các tập hợp.

Tạo danh sách

Trong nhiệm vụ này, bạn sẽ xem lại cách tạo danh sách các số nguyên và sắp xếp các số này theo thứ tự.

  1. Mở Kotlin Playground.
  2. Thay thế mã bằng đoạn mã bên dưới:
fun main() {
    val numbers = listOf(0, 3, 8, 4, 0, 5, 5, 8, 9, 2)
    println("list:   ${numbers}")
}
  1. Nhấn vào mũi tên màu xanh lục để chạy chương trình này và xem kết quả:
list:   [0, 3, 8, 4, 0, 5, 5, 8, 9, 2]
  1. Danh sách này chứa 10 số, mỗi số có giá trị từ 0 đến 9. Một số giá trị xuất hiện nhiều lần trong danh sách, trong khi một số giá trị khác hoàn toàn không xuất hiện.
  2. Thứ tự của các phần tử trong danh sách này rất quan trọng: phần tử đầu tiên là 0, phần tử thứ hai là 3, v.v. Các phần tử này sẽ giữ nguyên thứ tự đó trừ phi bạn thay đổi.
  3. Hãy nhớ lại các lớp học lập trình trước đây, danh sách có nhiều hàm tích hợp sẵn, chẳng hạn như hàm sorted(), trả về bản sao của danh sách được sắp xếp theo thứ tự tăng dần. Sau lệnh println(), hãy thêm một dòng vào chương trình của bạn để in bản sao danh sách đã được sắp xếp:
println("sorted: ${numbers.sorted()}")
  1. Chạy lại chương trình và xem kết quả:
list:   [0, 3, 8, 4, 0, 5, 5, 8, 9, 2]
sorted: [0, 0, 2, 3, 4, 5, 5, 8, 8, 9]

Các con số trong danh sách đã được sắp xếp theo thứ tự, giúp bạn dễ dàng biết được số lần xuất hiện của mỗi số trong danh sách hoặc kiểm tra xem một số có xuất hiện trong danh sách hay không.

Tìm hiểu về tập hợp set

Một kiểu tập hợp khác trong Kotlin là tập hợp set. Tập hợp này chứa một nhóm các phần tử có liên quan, nhưng không giống như danh sách, các phần tử trong tập hợp set không được trùng nhau và thứ tự các phần tử không quan trọng. Một phần tử có thể thuộc hoặc không thuộc tập hợp set, nhưng nếu phần tử đó thuộc tập hợp set thì phần tử này phải là duy nhất. Điều này cũng tương tự như khái niệm toán học về một tập hợp. Ví dụ: có một tập hợp các quyển sách bạn đã đọc. Việc đọc một quyển sách nhiều lần không làm thay đổi thực tế rằng cuốn sách đó nằm trong tập hợp các quyển sách bạn đã đọc.

  1. Thêm các dòng lệnh sau vào chương trình của bạn để chuyển đổi danh sách trên thành một tập hợp set:
val setOfNumbers = numbers.toSet()
println("set:    ${setOfNumbers}")
  1. Hãy chạy chương trình của bạn và xem kết quả:
list:   [0, 3, 8, 4, 0, 5, 5, 8, 9, 2]
sorted: [0, 0, 2, 3, 4, 5, 5, 8, 8, 9]
set:    [0, 3, 8, 4, 5, 9, 2]

Kết quả chứa tất cả con số trong danh sách ban đầu, nhưng mỗi số chỉ xuất hiện một lần. Lưu ý rằng các phần tử đang sắp xếp theo đúng thứ tự như trong danh sách ban đầu, nhưng thứ tự này không quan trọng đối với tập hợp set.

  1. Định nghĩa một tập hợp set thay đổi được và một tập hợp set không thay đổi được. Sau đó khởi tạo các tập hợp này sử dụng cùng một tập các số nguyên nhưng được sắp xếp theo thứ tự khác nhau. Thực hiện điều này bằng cách thêm các dòng lệnh sau:
val set1 = setOf(1,2,3)
val set2 = mutableSetOf(3,2,1)
  1. Thêm một dòng lệnh để in kiểm tra xem các tập hợp đó có bằng nhau hay không:
println("$set1 == $set2: ${set1 == set2}")
  1. Chạy chương trình của bạn và xem kết quả cập nhật:
[1, 2, 3] == [3, 2, 1]: true

Mặc dù một tập hợp là thay đổi được và tập hợp kia là không thay đổi được, đồng thời các phần tử trong hai tập hợp này được sắp xếp theo thứ tự khác nhau, nhưng hai tập hợp này vẫn được xem là bằng nhau vì chứa chính xác cùng một tập hợp các phần tử.

Một trong những thao tác chính có thể thực hiện trên tập hợp set là kiểm tra xem một phần tử cụ thể nào đó có thuộc tập hợp set hay không bằng hàm contains(). Bạn đã sử dụng hàm contains() trước đây khi thao tác với một danh sách.

  1. Thêm dòng lệnh này vào chương trình của bạn để in ra kết quả nếu 7 thuộc tập hợp set đã tạo:
println("contains 7: ${setOfNumbers.contains(7)}")
  1. Chạy chương trình của bạn và xem kết quả bổ sung:
contains 7: false

Bạn cũng có thể kiểm tra kết quả với một giá trị nằm trong tập hợp set.

Dưới đây là toàn bộ mã nêu trên:

fun main() {
    val numbers = listOf(0, 3, 8, 4, 0, 5, 5, 8, 9, 2)
    println("list:   ${numbers}")
    println("sorted: ${numbers.sorted()}")
    val setOfNumbers = numbers.toSet()
    println("set:    ${setOfNumbers}")
    val set1 = setOf(1,2,3)
    val set2 = mutableSetOf(3,2,1)
    println("$set1 == $set2: ${set1 == set2}")
    println("contains 7: ${setOfNumbers.contains(7)}")
}

Cũng như các tập hợp trong toán học, với Kotlin, bạn cũng có thể thực hiện các phép toán như phép giao (∩) hoặc phép hợp (∪) của hai tập hợp, bằng cách sử dụng intersect() hoặc union().

Tìm hiểu về tập hợp map

Kiểu tập hợp cuối cùng bạn sẽ tìm hiểu trong lớp học lập trình này là map hoặc dictionary. Map là một tập hợp các cặp khoá-giá trị, được thiết kế để giúp bạn dễ dàng tìm kiếm một giá trị dựa trên một khoá cụ thể cho trước. Các khoá là duy nhất và mỗi khoá sẽ liên kết với đúng một giá trị, nhưng các giá trị này có thể trùng nhau. Giá trị trong tập hợp map có thể là chuỗi, số hoặc đối tượng – thậm chí một tập hợp khác (như một danh sách hoặc một tập hợp set).

b55b9042a75c56c0.png

Tập hợp map sẽ rất hữu ích khi bạn có các cặp dữ liệu và muốn xác định từng cặp dựa trên khoá của mỗi cặp. Khoá này sẽ "liên kết" tới giá trị tương ứng.

  1. Trong Kotlin Playground, hãy thay thế toàn bộ mã bằng mã bên dưới để tạo một tập hợp map thay đổi được nhằm lưu trữ tên người và độ tuổi:
fun main() {
    val peopleAges = mutableMapOf<String, Int>(
        "Fred" to 30,
        "Ann" to 23
    )
    println(peopleAges)
}

Thao tác này sẽ tạo tập hợp map thay đổi được với String (khoá) liên kết với Int (giá trị), khởi tạo tập hợp map với hai phần tử được nhập vào và in các phần tử này.

  1. Hãy chạy chương trình của bạn và xem kết quả:
{Fred=30, Ann=23}
  1. Để thêm các phần tử khác vào tập hợp map này, bạn có thể sử dụng hàm put(), truyền vào khoá và giá trị:
peopleAges.put("Barbara", 42)
  1. Bạn cũng có thể sử dụng ký hiệu viết tắt để thêm các phần tử:
peopleAges["Joe"] = 51

Sau đây là toàn bộ mã sử dụng ở phần trên:

fun main() {
    val peopleAges = mutableMapOf<String, Int>(
        "Fred" to 30,
        "Ann" to 23
    )
    peopleAges.put("Barbara", 42)
    peopleAges["Joe"] = 51
    println(peopleAges)
}
  1. Chạy chương trình của bạn và xem kết quả:
{Fred=30, Ann=23, Barbara=42, Joe=51}

Như đã lưu ý ở trên, các khoá (tên) là duy nhất, nhưng các giá trị (tuổi) có thể có trùng nhau. Điều gì sẽ xảy ra nếu bạn cố gắng thêm một phần tử có khoá trùng với các khoá ở trên?

  1. Trước lệnh println(), hãy thêm dòng mã sau:
peopleAges["Fred"] = 31
  1. Chạy chương trình của bạn và xem kết quả:
{Fred=31, Ann=23, Barbara=42, Joe=51}

Khoá "Fred" không được thêm vào nữa, nhưng giá trị mà khoá này liên kết đến được cập nhật thành 31.

Như bạn đã thấy, tập hợp map rất hữu ích, giúp liên kết nhanh chóng các khoá với các giá trị trong mã của bạn!

3. Làm việc với tập hợp

Mỗi kiểu tập hợp đều có những nét đặc trưng riêng nhưng các kiểu tập hợp này vẫn có nhiều hành vi chung. Nếu các tập hợp này có thể thay đổi được, bạn có thể thêm hoặc xoá các phần tử. Bạn có thể liệt kê tất cả phần tử, tìm một phần tử cụ thể hoặc thỉnh thoảng chuyển đổi một kiểu tập hợp này sang một kiểu tập hợp khác. Bạn đã thực hiện việc này trước đây khi chuyển List thành Set bằng hàm toSet(). Dưới đây là một số hàm hữu ích khi làm việc với các tập hợp.

forEach

Giả sử bạn muốn in các phần tử trong peopleAges, bao gồm tên và tuổi của người đó. Ví dụ: "Fred is 31, Ann is 23,...". Bạn đã tìm hiểu về vòng lặp for trong lớp học lập trình trước đó, vì vậy, bạn có thể viết một vòng lặp là for (people in peopleAges) { ... }.

Tuy nhiên, thao tác liệt kê tất cả đối tượng trong một tập hợp là một thao tác phổ biến, vì vậy, Kotlin cung cấp hàm forEach(). Hàm này sẽ duyệt qua mọi phần tử và thực hiện một thao tác nào đó trên mỗi phần tử.

  1. Trong Kotlin Playground, hãy thêm mã này sau lệnh println():
peopleAges.forEach { print("${it.key} is ${it.value}, ") }

Hàm này tương tự như vòng lặp for nhưng cách viết gọn gàng hơn. Thay vì chỉ định một biến cho phần tử hiện tại, forEach sẽ sử dụng giá trị nhận dạng đặc biệt it.

Lưu ý rằng bạn không cần thêm dấu ngoặc đơn khi gọi phương thức forEach(), chỉ cần truyền đoạn mã này trong dấu ngoặc nhọn {}.

  1. Chạy chương trình của bạn và xem kết quả bổ sung:
Fred is 31, Ann is 23, Barbara is 42, Joe is 51,

Kết quả này gần đạt kỳ vọng của bạn nhưng vẫn bị thừa một dấu phẩy ở cuối.

Chuyển đổi một tập hợp thành một chuỗi là một thao tác phổ biến, tương tự như vấn đề thừa dấu phân tách ở cuối chuỗi kết quả. Bạn sẽ tìm hiểu cách xử lý vấn đề đó trong các bước tiếp theo.

map

Hàm map() (không nên nhầm lẫn với tập hợp map hoặc dictionary ở trên) áp dụng phép biến đổi trên từng phần tử trong tập hợp.

  1. Trong chương trình của bạn, hãy thay thế câu lệnh forEach bằng dòng sau:
println(peopleAges.map { "${it.key} is ${it.value}" }.joinToString(", ") )
  1. Chạy chương trình của bạn và xem kết quả bổ sung:
Fred is 31, Ann is 23, Barbara is 42, Joe is 51

Kết quả này đã chính xác và không thừa dấu phẩy ở cuối! Có nhiều thứ đang diễn ra trong dòng lệnh trên, chúng ta cần phân tích kỹ hơn.

  • peopleAges.map áp dụng phép biến đổi cho từng phần tử trong peopleAges và tạo một tập hợp mới các phần tử đã chuyển đổi.
  • Phần trong dấu ngoặc nhọn {} định nghĩa phép biến đổi áp dụng cho từng phần tử. Phép biến đổi này nhận một cặp khoá-giá trị và chuyển đổi cặp dữ liệu này thành một chuỗi, ví dụ: <Fred, 31> chuyển thành Fred is 31.
  • joinToString(", ") thêm từng phần tử trong tập hợp đã chuyển đổi vào một chuỗi, phân tách bằng dấu , và đồng thời không thêm dấu phân tách này vào phần tử cuối cùng
  • Tất cả thao tác này được liên kết với nhau bằng dấu . (toán tử dấu chấm), tương tự như cách bạn thực hiện lệnh gọi hàm và truy cập thuộc tính trong lớp học lập trình trước đó

filter

Một thao tác phổ biến khác trên tập hợp là tìm các phần tử thoả một điều kiện cụ thể nào đó. Hàm filter() trả về các phần tử trong một tập hợp thoả điều kiện của một biểu thức nào đó.

  1. Sau lệnh println(), hãy thêm các dòng sau:
val filteredNames = peopleAges.filter { it.key.length < 4 }
println(filteredNames)

Lưu ý rằng lời gọi hàm filter không cần dấu ngoặc đơn và it ám chỉ đến phần tử hiện tại trong danh sách.

  1. Chạy chương trình của bạn và xem kết quả bổ sung:
{Ann=23, Joe=51}

Trong trường hợp này, biểu thức lấy độ dài của khoá (String) và kiểm tra xem độ dài này có nhỏ hơn 4 hay không. Bất kỳ phần tử nào có độ dài tên nhỏ hơn 4 ký tự sẽ được thêm vào tập hợp mới.

Khi áp dụng hàm filter trên một tập hợp map thì dữ liệu trả về sẽ là một tập hợp map mới (LinkedHashMap). Bạn có thể xử lý thêm trên tập hợp map này hoặc chuyển đổi tập hợp này sang một kiểu tập hợp khác như danh sách chẳng hạn.

4. Tìm hiểu về lambda và các hàm bậc cao

Hàm lambda

Hãy xem lại ví dụ trước:

peopleAges.forEach { print("${it.key} is ${it.value}") }

Có một biến (peopleAges) gọi đến một hàm (forEach). Thay vì dùng dấu ngoặc sau tên hàm cùng với tham số truyền vào, bạn sẽ thấy một đoạn mã trong dấu ngoặc nhọn {} nằm sau tên hàm. Tương tự, mẫu này cũng xuất hiện trong đoạn mã sử dụng các hàm mapfilter ở bước trước. Biến peopleAges gọi đến hàm forEach và mã sẽ được viết trong dấu ngoặc nhọn.

Tương tự như cách bạn viết một hàm nhỏ trong dấu ngoặc nhọn, nhưng không có tên hàm. Ý tưởng này — sử dụng tức thì một hàm không tên làm biểu thức — là một khái niệm rất hữu ích, được gọi là biểu thức lambda hay viết tắt là lambda.

Điều này dẫn đến một chủ đề quan trọng về việc làm thế nào để có thể tương tác mạnh mẽ với các hàm sử dụng Kotlin. Bạn có thể lưu trữ hàm trong các biến và lớp, truyền các hàm dưới dạng đối số và thậm chí trả giá trị là các hàm. Bạn có thể xem hàm giống như một biến dữ liệu kiểu khác, chẳng hạn như Int hoặc String.

Kiểu dữ liệu của hàm

Để có thể xem hàm như một biến dữ liệu kiểu khác, Kotlin có một khái niệm là kiểu dữ liệu của hàm (kiểu hàm), trong đó bạn có thể định nghĩa kiểu dữ liệu cụ thể cho một hàm dựa trên các tham số đầu vào và giá trị trả về. Để định kiểu dữ liệu cho hàm, sử dụng định dạng như sau:

Ví dụ về định kiểu dữ liệu cho hàm: (Int) -> Int

Một hàm có kiểu như trên phải nhận một tham số có kiểu Int và trả về giá trị có kiểu Int. Trong phần ký pháp kiểu hàm, các tham số được liệt kê trong dấu ngoặc đơn (được phân tách bằng dấu phẩy nếu có nhiều tham số). Tiếp đến sẽ là dấu mũi tên ->và theo sau là kiểu dữ liệu trả về.

Những loại hàm nào sẽ đáp ứng tiêu chí này? Bạn có một biểu thức lambda để tính gấp ba lần giá trị đầu vào của một số nguyên, thể hiện như bên dưới. Trong cú pháp của biểu thức lambda, các tham số sẽ xuất hiện trước (đánh dấu trong hộp màu đỏ), tiếp theo là dấu mũi tên hàm và cuối cùng là phần thân hàm (đánh dấu trong hộp màu tím). Biểu thức cuối cùng trong lambda là giá trị trả về.

252712172e539fe2.png

Thậm chí bạn có thể lưu trữ lambda vào một biến, như minh hoạ trong sơ đồ bên dưới. Cú pháp sử dụng tương tự như cách bạn khai báo kiểu dữ liệu cơ bản cho một biến như Int. Quan sát phần màu sắc, bạn sẽ thấy tên biến (hộp màu vàng), kiểu dữ liệu của biến (hộp màu xanh dương) và giá trị của biến (hộp màu xanh lục). Biến triple được dùng để lưu trữ một hàm. Kiểu dữ liệu của hàm này là kiểu hàm (Int) -> Int và giá trị là biểu thức lambda { a: Int -> a * 3}.

  1. Hãy thử mã này trong Kotlin Playground. Định nghĩa và gọi hàm triple với giá trị truyền vào là một số, chẳng hạn như số 5. 4d3f2be4f253af50.png
fun main() {
    val triple: (Int) -> Int = { a: Int -> a * 3 }
    println(triple(5))
}
  1. Kết quả trả về sẽ là:
15
  1. Trong dấu ngoặc nhọn, bạn có thể bỏ phần khai báo tham số tường minh (a: Int), bỏ dấu mũi tên hàm (->) và chỉ định nghĩa phần thân hàm. Cập nhật hàm triple được khai báo trong hàm main và chạy mã đó.
val triple: (Int) -> Int = { it * 3 }
  1. Kết quả sẽ hiển thị giống nhau, nhưng bây giờ biểu thức lambda được viết ngắn gọn hơn! Để tìm hiểu thêm các ví dụ khác về lambda, hãy xem thêm tài nguyên này.
15

Hàm bậc cao

Bây giờ, bạn đã bắt đầu thấy được tính linh hoạt trong cách thao tác với các hàm trong Kotlin, chúng ta hãy tiếp tục thảo luận về một ý tưởng thực sự hiệu quả khác, hàm bậc cao hơn. Ý tưởng này là truyền một hàm (trong trường hợp này là lambda) đến một hàm khác hoặc trả về một hàm từ một hàm khác.

Như vậy, các hàm map, filterforEach đều là ví dụ về các hàm bậc cao vì tất cả những hàm này đều có tham số là một hàm. (Trong biểu thức lambda truyền vào hàm bậc cao filter, bạn có thể bỏ tham số duy nhất và dấu mũi tên và sử dụng tham số it.)

peopleAges.filter { it.key.length < 4 }

Dưới đây là ví dụ về một hàm bậc cao khác: sortedWith().

Nếu muốn sắp xếp danh sách các chuỗi, bạn có thể sử dụng phương thức sorted() tích hợp sẵn cho tập hợp. Tuy nhiên, nếu muốn sắp xếp danh sách theo độ dài của các chuỗi, bạn cần viết thêm mã để lấy độ dài và so sánh độ dài của 2 chuỗi. Kotlin cho phép bạn làm điều này bằng cách truyền một lambda vào tham số của phương thức sortedWith().

  1. Trong Playground, hãy tạo danh sách tên và in danh sách đó theo thứ tự tên như đoạn mã sau:
fun main() {
    val peopleNames = listOf("Fred", "Ann", "Barbara", "Joe")
    println(peopleNames.sorted())
}
  1. Bây giờ, hãy in danh sách được sắp xếp theo độ dài tên bằng cách truyền biểu thức lambda đến hàm sortedWith(). Lambda phải nhận hai tham số cùng kiểu và trả về một giá trị Int. Thêm dòng mã này sau câu lệnh println() trong hàm main().
println(peopleNames.sortedWith { str1: String, str2: String -> str1.length - str2.length })
  1. Hãy chạy chương trình của bạn và xem kết quả.
[Ann, Barbara, Fred, Joe]
[Ann, Joe, Fred, Barbara]

Biểu thức Lambda truyền vào hàm sortedWith() có hai tham số, str1Stringstr2String. Tiếp đó là dấu mũi tên hàm đứng trước phần thân hàm.

7005f5b6bc466894.png

Hãy nhớ rằng biểu thức cuối cùng trong lambda là giá trị trả về. Trong trường hợp này, lambda sẽ trả về sự khác biệt giữa độ dài của chuỗi đầu tiên và độ dài của chuỗi thứ hai, là một giá trị Int. Giá trị đó đáp ứng yêu cầu cần thiết cho việc sắp xếp: nếu str1 ngắn hơn str2 thì trả về giá trị nhỏ hơn 0. Nếu str1str2 có cùng độ dài thì trả về giá trị 0. Nếu str2 dài hơn str1 thì sẽ trả về giá trị lớn hơn 0. Bằng cách thực hiện một chuỗi so sánh đồng thời giữa hai Strings, hàm sortedWith() sẽ đưa ra danh sách tên theo thứ tự chiều dài tăng dần.

OnClickListener và OnKeyListener trong Android

Xâu chuỗi những gì bạn đã tìm hiểu về Android cho đến thời điểm hiện tại, bạn đã sử dụng lambda trong các lớp học lập trình trước đây, chẳng hạn như khi bạn thiết lập trình nghe lượt nhấp cho nút lệnh trong ứng dụng Máy tính tiền boa:

calculateButton.setOnClickListener{ calculateTip() }

Sử dụng lambda để thiết lập trình nghe lượt nhấp là cách viết tắt rất tiện lợi. Bên dưới là cách viết dài hơn cho phần mã ở trên và so sánh với phiên bản rút gọn. Bạn không cần hiểu toàn bộ chi tiết của phiên bản dài, nhưng hãy chú ý một số mẫu giữa hai phiên bản.

29760e0a3cac26a2.png

Hãy xem xét cách lambda có cùng kiểu hàm với phương thức onClick() trong OnClickListener (nhận một đối số View và trả về Unit, tức là không có giá trị trả về).

Phiên bản mã rút gọn có thể thực hiện được nhờ tính năng chuyển đổi SAM (Single-Abstract-Method (Phương thức trừu tượng duy nhất)) trong Kotlin. Kotlin chuyển đổi biểu thức lambda thành đối tượng OnClickListener. Đối tượng này sẽ thực thi phương thức trừu tượng duy nhất onClick(). Bạn chỉ cần đảm bảo rằng kiểu hàm của lambda khớp với kiểu hàm của hàm trừu tượng.

Do tham số view không bao giờ được sử dụng trong lambda nên bạn có thể bỏ tham số này. Tiếp đến, chúng ta chỉ cần phần thân hàm trong lambda.

calculateButton.setOnClickListener { calculateTip() }

Những khái niệm này khá rắc rối và đòi hỏi bạn phải mất một chút thời gian và kinh nghiệm để nắm bắt. Bạn hãy kiên nhẫn nhé! Hãy xem một ví dụ khác. Hãy nhớ lại khi thiết lập trình nghe phím trên trường văn bản "Phí dịch vụ" của ứng dụng tính tiền boa, bạn có thể ẩn bàn phím ảo khi nhấn phím Enter.

costOfServiceEditText.setOnKeyListener { view, keyCode, event -> handleKeyEvent(view, keyCode) }

Khi tìm kiếm OnKeyListener, bạn sẽ thấy phương thức trừu tượng này có các tham số onKey(View v, int keyCode, KeyEvent event) và trả về một giá trị Boolean. Nhờ tính năng chuyển đổi SAM trong Kotlin, bạn có thể truyền Lambda vào setOnKeyListener(). Bạn chỉ cần đảm bảo lambda này có kiểu hàm là (View, Int, KeyEvent) -> Boolean.

Dưới đây là sơ đồ của biểu thức lambda ở trên. Các tham số là view, keyCode và event. Phần thân hàm bao gồm handleKeyEvent(view, keyCode) sử dụng các tham số truyền vào và trả về giá trị Boolean.

f73fe767b8950123.png

5. Tạo danh sách từ

Tiếp theo, hãy cùng áp dụng tất cả những gì bạn đã tìm hiểu về tập hợp, lambda và các hàm bậc cao vào một trường hợp sử dụng thực tế.

Giả sử bạn muốn tạo ứng dụng Android để chơi trò chơi đố chữ hoặc học từ vựng. Ứng dụng có giao diện như bên dưới, một nút tương ứng cho mỗi chữ cái trong bảng chữ cái:

7539df92789fad47.png

Khi nhấp vào chữ cái A, bạn sẽ thấy một danh sách ngắn gồm một số từ bắt đầu bằng chữ A và tương tự như thế với các chữ cái khác.

Bạn cần có một tập hợp các từ nhưng đó là loại tập hợp nào? Nếu ứng dụng sẽ chứa một số từ bắt đầu bằng từng chữ cái trong bảng chữ cái, bạn cần tìm cách hoặc sắp xếp tất cả từ vựng bắt đầu bằng một chữ cái cho trước. Để bài toán thêm phần thử thách, bạn sẽ chọn các từ khác nhau từ tập hợp của mình khi người dùng chạy ứng dụng.

Đầu tiên, hãy bắt đầu với một danh sách từ. Với một ứng dụng trong thực tế, bạn sẽ muốn một danh sách từ dài hơn và bao gồm các từ bắt đầu bằng tất cả chữ cái trong bảng chữ cái. Tuy nhiên, trong ứng dụng này, chỉ cần sử dụng một danh sách ngắn là đủ.

  1. Thay thế đoạn mã trong Kotlin Playground bằng mã này:
fun main() {
    val words = listOf("about", "acute", "awesome", "balloon", "best", "brief", "class", "coffee", "creative")
}
  1. Để tạo một tập hợp các từ bắt đầu bằng chữ B, bạn có thể sử dụng hàm filter với tham số là biểu thức lambda. Thêm các dòng lệnh sau:
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
println(filteredWords)

Hàm startsWith() trả về true nếu một chuỗi bắt đầu bằng chữ cái đã chỉ định. Bạn cũng có thể yêu cầu hàm này không phân biệt chữ hoa và chữ thường, nghĩa là "b" sẽ khớp với "b" hoặc "B".

  1. Hãy chạy chương trình của bạn và xem kết quả:
[balloon, best, brief]
  1. Hãy nhớ rằng bạn muốn các từ được sắp xếp ngẫu nhiên trong ứng dụng. Với các tập hợp Kotlin, bạn có thể sử dụng hàm shuffled() để tạo một bản sao của một tập hợp trong đó các phần tử được trộn lẫn ngẫu nhiên. Đồng thời, thay đổi các từ đã lọc để trộn lẫn:
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
    .shuffled()
  1. Chạy chương trình của bạn và xem kết quả cập nhật:
[brief, balloon, best]

Do các từ được trộn lẫn ngẫu nhiên nên thứ tự xuất hiện các từ khác với kết quả trên.

  1. Bạn không muốn hiển thị tất cả từ (đặc biệt là khi danh sách từ thực sự rất dài) mà chỉ cần một vài từ trong số đó. Bạn có thể sử dụng hàm take() để lấy các phần tử đầu tiên trong tập hợp. Lấy hai từ đầu tiên trong danh sách các từ đã được lọc:
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
    .shuffled()
    .take(2)
  1. Chạy chương trình của bạn và xem kết quả cập nhật:
[brief, balloon]

Một lần nữa, hãy nhớ rằng do các từ được trộn lẫn ngẫu nhiên, kết quả hiển thị sẽ khác nhau mỗi lần bạn chạy lại ứng dụng.

  1. Cuối cùng, đối với ứng dụng, bạn muốn sắp xếp danh sách từ ngẫu nhiên cho mỗi chữ cái. Tương tự như trước, bạn có thể sử dụng hàm sorted() để trả về bản sao của tập hợp này với các phần tử đã được sắp xếp:
val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
    .shuffled()
    .take(2)
    .sorted()
  1. Chạy chương trình của bạn và xem kết quả cập nhật:
[balloon, brief]

Dưới đây là toàn bộ mã sử dụng ở trên:

fun main() {
    val words = listOf("about", "acute", "awesome", "balloon", "best", "brief", "class", "coffee", "creative")
    val filteredWords = words.filter { it.startsWith("b", ignoreCase = true) }
        .shuffled()
        .take(2)
        .sorted()
    println(filteredWords)
}
  1. Hãy thử thay đổi mã này để tạo danh sách một từ ngẫu nhiên bắt đầu bằng chữ c. Bạn cần thay đổi gì cho đoạn mã ở trên?
val filteredWords = words.filter { it.startsWith("c", ignoreCase = true) }
    .shuffled()
    .take(1)

Trong ứng dụng thực tế, bạn cần áp dụng bộ lọc đối với từng chữ cái của bảng chữ cái. Tuy nhiên, bây giờ bạn đã biết cách tạo danh sách từ cho mỗi chữ cái!

Các tập hợp rất mạnh mẽ và linh hoạt. Chúng ta có thể sử dụng tập hợp để thực hiện nhiều công việc khác nhau cũng như xử lý vấn đề theo nhiều cách khác nhau. Khi tìm hiểu thêm về lập trình, bạn sẽ tìm hiểu cách xác định kiểu tập hợp nào sẽ phù hợp với vấn đề đang gặp phải cũng như cách tốt nhất để xử lý vấn đề đó.

Lambda và các hàm cấp cao sẽ giúp bạn thao tác với tập hợp dễ dàng hơn cũng như viết mã ngắn gọn hơn. Những ý tưởng này rất hữu ích và được sử dụng thường xuyên trong lập trình.

6. Tóm tắt

  • Một tập hợp là một nhóm các phần tử có liên quan với nhau
  • Tập hợp có thể thay đổi được hoặc không thay đổi được
  • Tập hợp có thể theo thứ tự hoặc không theo thứ tự
  • Tập hợp có thể chứa các phần tử duy nhất hoặc các phần tử có thể trùng nhau
  • Kotlin hỗ trợ nhiều kiểu tập hợp khác nhau, bao gồm danh sách, tập hợp set và tập hợp map
  • Kotlin cung cấp nhiều hàm khác nhau để xử lý và biến đổi tập hợp, bao gồm forEach, map, filter, sorted và nhiều hàm khác.
  • Lambda là một hàm không tên và có thể được truyền tức thì dưới dạng một biểu thức. Ví dụ như { a: Int -> a * 3 }.
  • Hàm bậc cao là hàm cho phép truyền một hàm vào một hàm khác hoặc trả về một hàm từ một hàm khác.

7. Tìm hiểu thêm