Giới thiệu
Kotlin sinh ra extension function làm cho việc tuân thủ nguyên tắc open/close trở nên dễ dàng hơn. Với extension function ta không cần phải kế thừa từ một class khi muốn mở rộng chức năng.
Có thể thấy extension function là thứ rất mạnh trong kotlin, cũng nhờ nó mà collections trong kotlin trở lên mạnh mẽ và làm cho việc lập trình đơn giản hơn rất nhiều.
Bài viết này mình sẽ giới thiệu một số chức năng trong collections kotlin:
- filter
- map
- flatMap
- groupBy
- partition
1. filter
Chức năng này trả về một list mới gồm các phần tử thỏa mãn predicate.
inline fun <T> Iterable<T>.filter(
predicate: (T) -> Boolean
): List<T>
Ví dụ trả về danh sách các số chẵn:
listOf(1,2,3,4,5).filter { number->
number%2 == 0
}
2. map
Chức năng này giúp chuyển đổi một collection thành một list sau phép biến đổi hàm ánh xạ transform T -> R mà ta định nghĩa.
inline fun <T, R> Iterable<T>.map(
transform: (T) -> R
): List<R>
Kiểu trả về của danh sách sau phép map phụ thuộc vào kiểu trả về của lambda transform.
Ví dụ: Cho một danh sách sinh viên, điểm gpa cuối khóa sẽ được sử dụng để xét làm giảng viên. Một sinh viên được làm giảng viên nếu GPA > 3.8. Trả về danh sách giảng viên.
Ta sẽ kết hợp filter và map để làm ví dụ xàm này.
data class Student(val name:String, val gpa: Float)
data class Teacher(val name:String)
val students = getStudents()
val teachers = students.filter { it.gpa > 3.8 }
.map { Teacher(it.name) }
Có thể thấy dùng filter + map làm code ngắn gọn dễ hiểu hơn nhiều đúng không nào.
3. flatMap
Nhìn thằng này chả khác gì thằng map ngoài thêm prefix flat, với một thằng ngu english như mình sau khi dịch flat thì chị google cho mình kết quả là phẳng, phẳng như nào thì cùng xem tiếp nhé.
Đây là hàm flatMap
inline fun <T, R> Iterable<T>.flatMap(
transform: (T) -> Iterable<R>
): List<R>
flatMap khác map duy nhất chỗ kiểu trả về của lambda transform là Iterable<R>
thay vì R
.
flatMap sẽ map mỗi phần tử kiểu T
thành một Iterable<R>
.
Cùng xem cách mà flatMap hoạt động dưới đây ta sẽ hiểu sao lại phẳng.
Các list trả về sau khi mỗi phần tử transform sẽ được addAll vào một list duy nhất là destination. Nếu dùng map thì sẽ trả về một list mà các phần tử trong đó là các list có độ dài khác nhau.
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
//Trả về giá trị của hàm flatMapTo
//transform là phép biến đổi T -> Iterable<R> mà ta truyền vào
return flatMapTo(ArrayList<R>(), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
//Duyệt tất cả phần tử trong danh sách ban đầu
for (element in this) {
//Với mỗi phần tử biến đổi thành một list bởi hàm transform
val list = transform(element)
//Thêm vào arrayList
destination.addAll(list)
}
return destination
}
flatMap rất hữu ích khi muốn transform một list với quan hệ 1-N.
Ví dụ: Một ứng dụng nghe nhạc có chức năng cho người dùng chọn nhiều thể loại và trả về tất cả các bài hát trong các thể loại đó. Người dùng lại tài khoản vip nên mỗi bài hát lấy ra sẽ được chuyển định dạng thành vip.
Ta có thể thấy thể loại và bài hát có quan hệ 1-N ta nghĩ ngay đến flatMap.
data class Song(val name: String, val singer: String)
data class VipSong(val name: String, val singer: String)
data class Category(val name: String, val songs: List<Song>)
...
val categories = listOf<Category>()
val vipSongs = categories.flatMap { category ->
category.songs.map { VipSong(it.name, it.singer) }
}
4. groupBy
Hàm này có chức năng nhóm các phần tử trong collection theo selector và trả về một map collection với các key theo selector. Ví dụ: Liệt kê các học sinh theo tên cuong -> {Student("cuong", id = 1)}
data class Grade(val math: Float, val physics: Float)
data class Student(val name: String, val grade: Grade)
...
val students = listOf(
Student("Cuong1", Grade(1f,2f)),
Student("Cuong2", Grade(3f,3f)),
Student("Cuong3", Grade(1f,2f)),
Student("Cuong5", Grade(1.2f,2.5f))
)
print(students.groupBy {
it.grade
})
Kết quả sẽ trả về một map collection:
{
Grade(math=1.0, physics=2.0)=[Student(name=Cuong1, grade=Grade(math=1.0, physics=2.0)), Student(name=Cuong3, grade=Grade(math=1.0, physics=2.0))],
Grade(math=3.0, physics=3.0)=[Student(name=Cuong2, grade=Grade(math=3.0, physics=3.0))],
Grade(math=1.2, physics=2.5)=[Student(name=Cuong5, grade=Grade(math=1.2, physics=2.5))]
}
5. Partition
Hàm này có chức năng phân chia collection thành hai list theo điều kiện, một list là thỏa mãn điều kiện, một list là không thỏa mãn điều kiện.
Ví dụ: Cần lấy danh sách sinh viên được tốt nghiệp và sinh viên chưa được tốt nghiệp
data class Student(val name: String, val gpa: Float, val credits: Int)
fun isGraduated(student: Student) = student.run { gpa>=2.5 && credits == 150}
fun main() {
val students = listOf(
Student("Lan", 4.0f, 140),
Student("Phuong", 2.45f, 150),
Student("MA", 2.7f, 150)
)
val (graduated, undergraduate) = students.partition(::isGraduated)
println("""
graduated: $graduated
undergraduate: $undergraduate
""".trimIndent())
}
Kết quả:
graduated: [Student(name=MA, gpa=2.7, credits=150)]
undergraduate: [Student(name=Lan, gpa=4.0, credits=140), Student(name=Phuong, gpa=2.45, credits=150)]
Kết luận
Các chức năng này giúp chúng ta giảm thiếu số lượng mã cũng như code clear hơn, nó cũng quan trọng và gặp thường xuyên khi làm việc với các thư viện reactive như Rx, flow coroutine.