List là loại collection tích hợp sẵn phổ biến nhất trong Kotlin. Quyền truy cập chỉ số vào các phần tử của list cung cấp cho người dùng một tập hợp các thao tác hữu ích trên list.
Nội dung chính
1. Truy xuất các phần tử theo chỉ số
List hỗ trợ tất cả các thao tác phổ biến để truy hồi phần tử: các hàm như elementAt(), first(), last(), và những hàm khác được liệt kê trong bài viết truy hồi phần tử đơn lẻ . Điều đặc biệt đối với list là quyền truy cập chỉ số vào các phần tử, vì vậy cách đơn giản nhất để đọc một phần tử là truy xuất nó bằng chỉ số. Điều đó được thực hiện với hàm get() có chỉ số được truyền trong đối số hoặc cú pháp viết tắt [index].
Nếu kích thước list nhỏ hơn chỉ số được chỉ định, một ngoại lệ sẽ được ném ra. Có hai hàm khác giúp bạn tránh những trường hợp ngoại lệ như vậy:
- Hàm getOrElse() cho phép bạn cung cấp hàm phục vụ cho việc tính toán giá trị mặc định để trả về nếu chỉ số không có trong collection.
- Hàm getOrNull() trả về giá trị null theo mặc định.
val numbers = listOf(1, 2, 3, 4)
println(numbers.get(0))
println(numbers[0])
//numbers.get(5) // exception!
println(numbers.getOrNull(5)) // null
println(numbers.getOrElse(5, {it})) // 5
2. Truy xuất các phần trong list
Ngoài các thao tác phổ biến để lấy các phần của collection , list cung cấp hàm subList() trả về dạng xem của một phạm vi phần tử cụ thể dưới dạng list. Do đó, nếu một phần tử của tập hợp ban đầu thay đổi, nó cũng thay đổi trong list con đã tạo trước đó và ngược lại.
val numbers = (0..13).toList()
println(numbers.subList(3, 6))
3. Tìm vị trí phần tử
3.1. Tìm kiếm tuyến tính
Trong bất kỳ list nào, bạn có thể tìm thấy vị trí của một phần tử bằng cách sử dụng các hàm indexOf() và lastIndexOf(). Chúng lần lượt trả về vị trí đầu tiên và cuối cùng của một phần tử bằng đối số đã cho trong list. Nếu không có phần tử nào như vậy, cả hai hàm đều trả về -1.
val numbers = listOf(1, 2, 3, 4, 2, 5)
println(numbers.indexOf(2))
println(numbers.lastIndexOf(2))
Ngoài ra còn có một cặp hàm nhận một vị từ và tìm kiếm các phần tử phù hợp với nó:
- Hàm indexOfFirst() trả về chỉ số của phần tử đầu tiên khớp với vị từ hoặc -1 nếu không có phần tử nào như vậy.
- Hàm indexOfLast() trả về chỉ số của phần tử cuối cùng khớp với vị từ hoặc -1 nếu không có phần tử nào như vậy.
val numbers = mutableListOf(1, 2, 3, 4)
println(numbers.indexOfFirst { it > 2})
println(numbers.indexOfLast { it % 2 == 1})
3.2. Tìm kiếm nhị phân trong list được sắp xếp
Có một cách nữa để tìm kiếm các phần tử trong list – đó chính là tìm kiếm nhị phân . Nó hoạt động nhanh hơn đáng kể so với các hàm tìm kiếm tích hợp sẵn khác nhưng đổi lại, nó yêu cầu list phải được sắp xếp một cách tăng dần theo một thứ tự nhất định: thứ tự tự nhiên hoặc một thứ tự khác được cung cấp trong tham số hàm. Nếu không, kết quả không được xác định.
Để tìm kiếm một phần tử trong list đã sắp xếp, hãy gọi hàm binarySearch() truyền giá trị dưới dạng đối số. Nếu một phần tử như vậy tồn tại, hàm trả về chỉ số của nó; nếu không, nó trả về (-insertionPoint – 1) trong đó insertionPointlà chỉ số nơi phần tử này cần được chèn vào để list vẫn giữ được sắp xếp sẵn có. Nếu có nhiều hơn một phần tử với giá trị đã cho, cách tìm kiếm này có thể trả về bất kỳ chỉ số nào của chúng.
Bạn cũng có thể chỉ định một phạm vi chỉ số để tìm kiếm: trong trường hợp này, hàm tìm kiếm chỉ tìm kiếm những phần tử có chỉ số giữa hai chỉ số được cung cấp.
val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println(numbers)
println(numbers.binarySearch("two")) // 3
println(numbers.binarySearch("z")) // -5
println(numbers.binarySearch("two", 0, 2)) // -3
3.2.1. Tìm kiếm nhị phân bằng Comparator
Khi các phần tử trong list không Comparable, bạn nên cung cấp một Comparator để sử dụng trong quá trình tìm kiếm nhị phân. List phải được sắp xếp theo thứ tự tăng dần theo Comparator. Hãy xem một ví dụ:
val productList = listOf(
Product("WebStorm", 49.0),
Product("AppCode", 99.0),
Product("DotTrace", 129.0),
Product("ReSharper", 149.0))
println(productList.binarySearch(Product("AppCode", 99.0), compareBy<Product> { it.price }.thenBy { it.name }))
Trên đây là list các instance của sản phẩm không Comparable và Comparator đã xác định thứ tự: sản phẩm p1 đứng trước sản phẩm p2 nếu giá của sản phẩm p1 nhỏ hơn giá của sản phẩm p2. Vì vậy, khi đã có một list được sắp xếp tăng dần theo thứ tự này, chúng ta có thể sử dụng hàm binarySearch() để tìm chỉ số của sản phẩm chỉ định.
Các Comparator tùy chỉnh cũng rất hữu ích khi list sử dụng thứ tự khác với thứ tự tự nhiên, chẳng hạn như thứ tự không phân biệt chữ hoa chữ thường cho các phần tử String.
val colors = listOf("Blue", "green", "ORANGE", "Red", "yellow")
println(colors.binarySearch("RED", String.CASE_INSENSITIVE_ORDER)) // 3
3.2.2. Tìm kiếm nhị phân với phép so sánh
Tìm kiếm nhị phân với phép so sánh cho phép bạn tìm các phần tử mà không cần cung cấp các giá trị tìm kiếm rõ ràng. Thay vào đó, nó có một hàm so sánh ánh xạ các phần tử tới các giá trị Int và tìm kiếm phần tử mà hàm trả về 0. List phải được sắp xếp theo thứ tự tăng dần thuận theo hàm đã cung cấp; nói cách khác, các giá trị trả về của phép so sánh phải tăng từ phần tử này sang phần tử tiếp theo.
data class Product(val name: String, val price: Double)
fun priceComparison(product: Product, price: Double) = sign(product.price - price).toInt()
fun main() {
val productList = listOf(
Product("WebStorm", 49.0),
Product("AppCode", 99.0),
Product("DotTrace", 129.0),
Product("ReSharper", 149.0))
println(productList.binarySearch { priceComparison(it, 99.0) })
}
Cả tìm kiếm nhị phân sử dụng Comparator và phép so sánh cũng có thể được thực hiện cho các phạm vi của list.
4. Các thao tác ghi trên list
Ngoài các thao tác sửa đổi collection được mô tả trong bài viết Các thao tác ghi trên collection, các list có thể thay đổi hỗ trợ các thao tác ghi cụ thể. Các thao tác như vậy sử dụng chỉ số để truy cập các phần tử để mở rộng khả năng sửa đổi list.
4.1. Thêm phần tử
Để thêm phần tử vào một vị trí cụ thể trong list, hãy sử dụng hàm add() và addAll() và cung cấp vị trí để chèn phần tử làm đối số bổ sung. Tất cả các phần tử ở sau vị trí đó đều dịch chuyển sang phải.
val numbers = mutableListOf("one", "five", "six")
numbers.add(1, "two")
numbers.addAll(2, listOf("three", "four"))
println(numbers)
4.2. Cập nhật
Các list cũng cung cấp một hàm để thay thế một phần tử ở một vị trí nhất định- đó là hàm set() và dạng toán tử của nó là []. set() không thay đổi chỉ số của các phần tử khác.
val numbers = mutableListOf("one", "five", "three")
numbers[1] = "two"
println(numbers)
Hàm fill() thay thế tất cả các phần tử trong collection với giá trị được chỉ định.
val numbers = mutableListOf(1, 2, 3, 4)
numbers.fill(3)
println(numbers)
4.3. Loại bỏ phần tử
Để xóa một phần tử tại một vị trí cụ thể khỏi list, hãy sử dụng hàm removeAt() và cung cấp vị trí làm đối số. Tất cả các chỉ số của phần tử đứng sau phần tử bị xóa sẽ giảm đi một.
val numbers = mutableListOf(1, 2, 3, 4, 3)
numbers.removeAt(1)
println(numbers)
Để loại bỏ phần tử đầu tiên và cuối cùng, có các hàm tiện dụng như removeFirst() và removeLast(). Lưu ý rằng trên các list trống, chúng ném ra một ngoại lệ. Để nhận null, hãy sử dụng hàm removeFirstOrNull() và removeLastOrNull()
val numbers = mutableListOf(1, 2, 3, 4, 3)
numbers.removeFirst()
numbers.removeLast()
println(numbers)
val empty = mutableListOf<Int>()
// empty.removeFirst() // NoSuchElementException: List is empty.
empty.removeFirstOrNull() //null
4.4. Sắp xếp
Trong bài Sắp xếp collection , chúng tôi đã mô tả các thao tác truy xuất các phần tử của collection theo các thứ tự cụ thể. Đối với list có thể thay đổi, thư viện chuẩn cung cấp các hàm mở rộng tương tự, các hàm này thực hiện các thao tác sắp xếp giống nhau tại chỗ. Khi bạn áp dụng một thao tác như vậy cho một instance của list, nó sẽ thay đổi thứ tự của các phần tử trong instance đó.
Các hàm sắp xếp tại chỗ có tên tương tự như các hàm áp dụng cho list chỉ đọc nhưng không có hậu tố ed/d:
- Trong tên của tất cả các hàm sắp xếp, ta dùng sort*thay vì sorted*: sort(), sortDescending(), sortBy(), và vân vân.
- shuffle()thay vì shuffled().
- reverse()thay vì reversed().
Hàm asReversed() được gọi trên list có thể thay đổi trả về một list có thể thay đổi khác, là dạng xem đã đảo ngược của list ban đầu. Những thay đổi trong chế độ xem đó được phản ánh trong list ban đầu. Ví dụ sau minh họa các hàm sắp xếp cho list có thể thay đổi:
val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println("Sort into ascending: $numbers")
numbers.sortDescending()
println("Sort into descending: $numbers")
numbers.sortBy { it.length }
println("Sort into ascending by length: $numbers")
numbers.sortByDescending { it.last() }
println("Sort into descending by the last letter: $numbers")
numbers.sortWith(compareBy<String> { it.length }.thenBy { it })
println("Sort by Comparator: $numbers")
numbers.shuffle()
println("Shuffle: $numbers")
numbers.reverse()
println("Reverse: $numbers")
Tài liệu từ cafedev:
- Full series tự học Kotlin từ cơ bản tới nâng cao tại đây nha.
- Ebook về Kotlin tại đây.
- Các series tự học lập trình khác
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!