Sử dụng các bộ sưu tập trong Kotlin

1. Giới thiệu

Trong nhiều ứng dụng, bạn có thể thấy dữ liệu được hiển thị dưới dạng danh sách: danh bạ, cài đặt, kết quả tìm kiếm, v.v.

9fbd3bf7cb6adc72.png

Tuy nhiên, trong các mã bạn viết cho đến giờ, chủ yếu dành cho dữ liệu bao gồm một giá trị duy nhất, chẳng hạn như một số hoặc đoạn văn bản hiển thị trên màn hình. Để tạo ứng dụng liên quan đến lượng dữ liệu tùy ý, bạn cần tìm hiểu cách sử dụng bộ sưu tập.

Các kiểu bộ sưu tập (đôi khi được gọi là cấu trúc dữ liệu) cho phép bạn lưu trữ nhiều giá trị, thường là cùng một loại dữ liệu theo cách ngăn nắp. Bộ sưu tập có thể là một danh sách được sắp xếp theo thứ tự, một nhóm các giá trị duy nhất hoặc mối liên kết các giá trị của một loại dữ liệu tới các giá trị của loại khác. Khả năng sử dụng hiệu quả bộ sưu tập cho phép bạn triển khai các tính năng phổ biến của ứng dụng Android, chẳng hạn như danh sách cuộn, cũng như giải quyết nhiều vấn đề trong lập trình thực tế liên quan đến lượng dữ liệu tùy ý.

Lớp học lập trình này thảo luận về cách làm việc với nhiều giá trị trong mã của bạn đồng thời giới thiệu nhiều cấu trúc dữ liệu khác nhau, bao gồm mảng, danh sách, tập hợp và bản đồ.

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

  • Làm quen với lập trình hướng đối tượng trong Kotlin, bao gồm các lớp, giao diện và khái niệm chung.

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

  • Cách tạo và sửa đổi mảng.
  • Cách sử dụng ListMutableList.
  • Cách sử dụng SetMutableSet.
  • Cách sử dụng MapMutableMap.

Bạn cần có

  • Một trình duyệt web có quyền truy cập vào Kotlin Playground.

2. Mảng trong Kotlin

Mảng là gì?

Mảng là cách đơn giản nhất để nhóm một số lượng giá trị tùy ý trong các chương trình của bạn.

Giống như một nhóm các bảng điều khiển năng lượng mặt trời được gọi là mảng năng lượng mặt trời, hoặc việc học chương trình Kotlin mở ra nhiều khả năng cho sự nghiệp lập trình của bạn, Array đại diện cho nhiều giá trị. Cụ thể, mảng là một chuỗi các giá trị mà tất cả đều có cùng một loại dữ liệu.

960e34f4c96e2fd9.png

  • Một mảng chứa nhiều giá trị được gọi là phần tử hoặc đôi khi là các mục.
  • Các phần tử trong một mảng được sắp xếp và truy cập bằng một chỉ mục.

Chỉ mục là gì? Chỉ mục là một số nguyên tương ứng với một phần tử trong mảng. Chỉ mục cho biết khoảng cách của một mục từ phần tử bắt đầu trong mảng. Hành động này được gọi là zero-indexing. Phần tử đầu tiên của mảng nằm ở chỉ mục 0, phần tử thứ hai ở chỉ mục 1, vì nó cách phần tử đầu tiên một vị trí, và cứ thế tiếp tục.

5baf880a3670720d.png

Trong bộ nhớ của thiết bị, các phần tử trong mảng được lưu trữ cạnh nhau. Mặc dù các chi tiết cơ bản nằm ngoài phạm vi của lớp học lập trình này nhưng đều có hai ý nghĩa quan trọng:

  • Quyền truy cập vào một phần tử mảng theo chỉ mục của phần tử này rất nhanh. Bạn có quyền truy cập vào bất kỳ phần tử ngẫu nhiên nào trong một mảng theo chỉ mục của nó và dự kiến sẽ mất cùng một khoảng thời gian để truy cập bất kỳ phần tử ngẫu nhiên nào khác. Đây là lý do vì sao các mảng được cho là có quyền truy cập ngẫu nhiên.
  • Một mảng thường có kích thước cố định. Điều này có nghĩa là bạn không thể thêm các phần tử vào một mảng vượt quá kích thước này. Việc cố gắng truy cập vào phần tử ở chỉ mục 100 trong một mảng gồm 100 phần tử sẽ tạo một ngoại lệ vì chỉ mục cao nhất là 99 (vui lòng lưu ý chỉ mục đầu tiên là 0 thay vì 1). Tuy nhiên, bạn có thể sửa đổi các giá trị tại chỉ mục trong mảng.

Để khai báo một mảng trong mã, bạn cần dùng hàm arrayOf().

9d5c8c00b30850cb.png

Hàm arrayOf() lấy các phần tử mảng ở dạng tham số và trả về một mảng thuộc loại khớp với các tham số đã được truyền vào. Việc này có thể trông hơi khác so với các hàm khác mà bạn thấy vì arrayOf() có nhiều tham số khác nhau. Nếu bạn truyền hai đối số vào arrayOf(), mảng kết quả sẽ chứa hai phần tử, được lập chỉ mục 0 và 1. Nếu bạn truyền ba đối số, mảng thu được sẽ có 3 phần tử, được lập chỉ mục từ 0 đến 2.

Hãy xem các mảng hoạt động qua khám phá nhỏ về hệ mặt trời!

  1. Chuyển đến phần Kotlin Playground.
  2. Trong main(), hãy tạo một biến rockPlanets. Gọi arrayOf(), truyền theo loại String, cùng với 4 chuỗi — một chuỗi cho mỗi hành tinh đá trong hệ mặt trời.
val rockPlanets = arrayOf<String>("Mercury", "Venus", "Earth", "Mars")
  1. Vì Kotlin sử dụng cách dự đoán theo loại, nên bạn có thể bỏ qua tên loại khi gọi arrayOf(). Bên dưới biến rockPlanets, hãy thêm một biến khác gasPlanets mà không cần truyền một loại vào trong dấu ngoặc nhọn.
val gasPlanets = arrayOf("Jupiter", "Saturn", "Uranus", "Neptune")
  1. Bạn sẽ thấy các mảng thú vị thế nào khi khám phá. Ví dụ: tương tự như loại số Int hoặc Double, bạn có thể thêm hai mảng với nhau. Tạo một biến mới có tên là solarSystem và đặt biến đó bằng kết quả của rockPlanetsgasPlanets, sử dụng toán tử dấu cộng (+). Kết quả là một mảng mới chứa tất cả các phần tử của mảng rockPlanetsgasPlanets.
val solarSystem = rockPlanets + gasPlanets
  1. Chạy chương trình để xác minh nó hoạt động đúng cách. Bạn chưa thấy dữ liệu đầu ra nào.

Truy cập phần tử trong một mảng

Bạn có thể truy cập vào phần tử của một mảng theo chỉ mục của mảng đó.

1f8398eaee30c7b0.png

Đây được gọi là cú pháp chỉ số dưới. Nó bao gồm ba phần:

  • Tên mảng.
  • Dấu ngoặc vuông mở ([) và đóng (]).
  • Chỉ mục của phần tử mảng trong dấu ngoặc vuông.

Vui lòng truy cập vào các phần tử của mảng solarSystem theo chỉ mục của chúng.

  1. Trong main(), hãy truy cập và in từng phần tử của mảng solarSystem. Vui lòng lưu ý chỉ mục đầu tiên là 0 và chỉ mục cuối cùng là 7.
println(solarSystem[0])
println(solarSystem[1])
println(solarSystem[2])
println(solarSystem[3])
println(solarSystem[4])
println(solarSystem[5])
println(solarSystem[6])
println(solarSystem[7])
  1. Chạy chương trình. Các phần tử phải theo đúng thứ tự mà bạn đã liệt kê khi gọi arrayOf().
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune

Bạn cũng có thể đặt giá trị của một phần tử mảng theo chỉ mục của nó.

9469e321ed79c074.png

Cách truy cập vào chỉ mục cũng tương tự như trước đây — nghĩa là tên của mảng, theo sau là dấu ngoặc vuông mở và đóng chứa chỉ mục. Tiếp theo là toán tử chỉ định (=) và một giá trị mới.

Hãy thực hành sửa đổi các giá trị trên mảng solarSystem.

  1. Hãy đặt một tên mới cho sao Hỏa phục vụ những người định cư trong tương lai. Truy cập phần tử tại chỉ mục 3 và đặt phần tử này bằng "Little Earth".
solarSystem[3] = "Little Earth"
  1. In phần tử tại chỉ mục 3.
println(solarSystem[3])
  1. Chạy chương trình. Phần tử thứ tư của mảng (tại chỉ mục 3) đã được cập nhật.
...
Little Earth
  1. Bây giờ, giả sử các nhà khoa học đã khám phá ra có một hành tinh thứ 9 nằm bên ngoài Neptune với tên gọi Pluto. Trước đó, chúng tôi đã đề cập việc không thể đổi kích thước mảng. Điều gì sẽ xảy ra nếu bạn thử thay đổi các kích thước mảng đó? Hãy thử thêm Pluto vào mảng solarSystem. Thêm Pluto tại chỉ mục 8 vì đây là phần tử thứ 9 trong mảng.
solarSystem[8] = "Pluto"
  1. Chạy mã. Thao tác này sẽ tạo một trường hợp ngoại lệ ArrayIndexOutOfBounds. Vì mảng đã có 8 phần tử như dự kiến, nên bạn không thể chỉ thêm một phần tử thứ chín.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 8 out of bounds for length 8
  1. Xóa Pluto đã thêm vào mảng.

Mã cần xóa

solarSystem[8] = "Pluto"
  1. Nếu sửa một mảng lớn hơn kích thước có sẵn, bạn cần phải tạo một mảng mới. Xác định một biến mới có tên là newSolarSystem như minh họa. Mảng này có thể lưu trữ 9 thay vì 8 phần tử.
val newSolarSystem = arrayOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto")
  1. Bây giờ, hãy thử in phần tử tại chỉ mục 8.
println(newSolarSystem[8])
  1. Chạy mã của bạn và quan sát xem liệu mã đó có chạy mà không tạo bất kỳ ngoại lệ nào không.
...
Pluto

Tuyệt vời! Với kiến thức hiện có về mảng, bạn có thể thỏa thích khám phá hầu hết mọi thứ về bộ sưu tập.

Đợi đã, thực sự không dễ dàng vậy đâu! Mặc dù mảng là một trong các thành phần cơ bản của lập trình, nhưng việc sử dụng mảng cho các tác vụ có yêu cầu thêm và xóa các phần tử, tính duy nhất trong một bộ sưu tập hoặc việc ánh xạ một đối tượng đến các đối tượng khác không hề đơn giản hoặc dễ hiểu khiến mã của ứng dụng sẽ nhanh chóng trở nên lộn xộn.

Đây là lý do hầu hết các ngôn ngữ lập trình, kể cả Kotlin, đều triển khai các loại bộ sưu tập đặc biệt để xử lý các tình huống thường xảy ra trong ứng dụng thực tế. Trong các phần sau, bạn sẽ tìm hiểu về ba bộ sưu tập phổ biến: List, SetMap. Bạn cũng sẽ tìm hiểu về các thuộc tính và phương thức phổ biến cũng như các trường hợp sử dụng các loại bộ sưu tập này.

3. Danh sách

Danh sách là một tập hợp có thể thay đổi kích thước, được sắp xếp theo thứ tự, thường được triển khai dưới dạng một mảng có thể thay đổi kích thước. Khi mảng đã đạt hạn mức nhưng bạn cố gắng chèn thêm một phần tử mới, mảng sẽ được sao chép vào một mảng mới lớn hơn.

a4970d42cd1d2b66.png

Với danh sách, bạn cũng có thể chèn các phần tử mới giữa các phần tử khác vào một chỉ mục cụ thể.

27afd8dd880e1ae5.png

Đây là cách các danh sách có thể thêm và xóa phần tử. Trong hầu hết các trường hợp, việc thêm bất kỳ phần tử nào vào danh sách cũng đều cần cùng một khoảng thời gian, bất kể có bao nhiêu phần tử trong danh sách. Đôi khi, nếu việc thêm một phần tử mới khiến mảng vượt quá kích thước đã xác định, thì các phần tử mảng có thể phải di chuyển để tạo chỗ trống cho các phần tử mới. Tính năng danh sách sẽ làm tất cả những điều này cho bạn, nhưng trên thực tế, việc này chỉ đơn giản là một mảng được hoán đổi cho một mảng mới khi cần.

ListMutableList

Các loại bộ sưu tập mà bạn sẽ gặp trong Kotlin khi triển khai một hoặc nhiều giao diện. Như chúng ta đã tìm hiểu ở lớp học lập trình trước đó trong chương này về Các thành phần chung, đối tượng và tiện ích, giao diện cung cấp một tập hợp các thuộc tính và phương thức chuẩn cho một lớp để triển khai. Một lớp triển khai giao diện List cung cấp các phương thức triển khai cho tất cả các thuộc tính và phương thức của giao diện List. Điều này cũng đúng đối với MutableList.

Vậy ListMutableList có chức năng gì?

  • List là một giao diện xác định các thuộc tính và phương thức liên quan đến một tập hợp các mục chỉ có thể đọc theo thứ tự.
  • MutableList mở rộng giao diện List bằng cách xác định các phương thức sửa đổi danh sách, chẳng hạn như thêm và xóa phần tử.

Các giao diện này chỉ chỉ định các thuộc tính và phương thức của List và/hoặc MutableList. Nó tùy thuộc vào lớp mở rộng để xác định cách triển khai từng thuộc tính cũng như phương thức. Việc triển khai dựa trên mảng được mô tả ở trên là cách bạn sẽ sử dụng nhiều nhất (nếu không phải lúc nào cũng được), nhưng Kotlin cho phép các lớp khác mở rộng ListMutableList.

Hàm listOf()

Tương tự như arrayOf(), hàm listOf() lấy các mục dưới dạng tham số, nhưng trả về List thay vì một mảng.

  1. Xóa mã hiện có khỏi main().
  2. Trong main(), hãy tạo một List các hành tinh có tên là solarSystem bằng cách gọi listOf().
fun main() {
    val solarSystem = listOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
}
  1. List chứa thuộc tính size để nhận số phần tử trong danh sách. In size của danh sách solarSystem.
println(solarSystem.size)
  1. Chạy mã. Kích thước của danh sách phải là 8.
8

Truy cập các phần tử trong một danh sách

Tương tự như mảng, bạn có thể truy cập vào phần tử trong một chỉ mục cụ thể từ một List bằng cú pháp chỉ số dưới. Bạn có thể thực hiện tương tự với phương thức get(). Cú pháp chỉ số dưới và phương thức get() lấy Int làm tham số và trả về phần tử ở chỉ mục đó. Tương tự như Array, ArrayList không được lập chỉ mục. Do đó, phần tử thứ tư sẽ nằm trong chỉ mục 3, chẳng hạn như vậy.

  1. In hành tinh tại chỉ mục 2 bằng cú pháp chỉ số dưới.
println(solarSystem[2])
  1. In phần tử tại chỉ mục 3 bằng cách gọi get() trên danh sách solarSystem.
println(solarSystem.get(3))
  1. Chạy mã. Phần tử ở chỉ mục 2"Earth" và phần tử ở chỉ mục 3"Mars".
...
Earth
Mars

Ngoài việc lấy một phần tử theo chỉ mục của nó, bạn cũng có thể tìm kiếm chỉ mục của một phần tử cụ thể bằng phương thức indexOf(). Phương thức indexOf() tìm kiếm danh sách cho một phần tử nhất định (được truyền dưới dạng đối số) và trả về chỉ mục trong lần xuất hiện đầu tiên của phần tử đó. Nếu không có phần tử nào trong danh sách, chỉ số được trả về sẽ là -1.

  1. In kết quả gọi indexOf() trên danh sách solarSystem, truyền qua "Earth".
println(solarSystem.indexOf("Earth"))
  1. Gọi indexOf(), truyền qua "Pluto" và in kết quả.
println(solarSystem.indexOf("Pluto"))
  1. Chạy mã. Phần tử khớp với "Earth", do đó, chỉ mục 2 sẽ được in. Không có phần tử nào khớp với "Pluto", vì vậy, -1 sẽ được in.
...
2
-1

Lặp lại các phần tử danh sách bằng cách sử dụng vòng lặp for

Khi tìm hiểu về các loại hàm và biểu thức lambda, bạn đã biết cách dùng hàm repeat() để thực thi mã nhiều lần.

Nhiệm vụ phổ biến trong lập trình là thực hiện nhiệm vụ một lần cho từng phần tử trong một danh sách. Kotlin có một tính năng gọi là vòng lặp for để thực hiện điều này bằng cú pháp ngắn gọn và dễ đọc. Thông thường bạn sẽ thấy hoạt động này được gọi là lặp lại theo một danh sách hoặc vòng lặp qua một danh sách.

1245a226a9ceeba1.png

Để lặp lại theo một danh sách, hãy sử dụng từ khóa for, theo sau là một cặp dấu ngoặc mở và đóng. Trong dấu ngoặc đơn, hãy thêm tên biến, theo sau là từ khóa in, tiếp đến là tên bộ sưu tập. Sau dấu ngoặc đơn đóng sẽ là một cặp dấu ngoặc nhọn mở và đóng, trong đó bạn phải thêm mã mình muốn thực thi cho từng phần tử trong bộ sưu tập. Nó được gọi là phần nội dung của vòng lặp. Mỗi lần mã này thực thi được gọi là một vòng lặp.

Không khai báo biến trước từ khóa in bằng val hoặc var – nó được coi là những giá trị chỉ nhận. Bạn có thể đặt bất kỳ tên nào bạn muốn. Nếu một danh sách có tên là số nhiều, chẳng hạn như planets, thì thông thường bạn nên đặt tên cho biến có dạng số ít, chẳng hạn như planet. Bạn cũng nên đặt tên cho biến item hoặc element.

Biến này sẽ được dùng làm biến tạm thời tương ứng với phần tử hiện tại trong bộ sưu tập – phần tử tại chỉ mục 0 cho lần lặp đầu tiên, phần tử ở chỉ mục 1 cho lần lặp thứ hai, v.v. và có thể truy cập được trong dấu ngoặc nhọn.

Để thực hiện được việc này, bạn sẽ in mỗi tên hành tinh trên một dòng riêng bằng cách sử dụng vòng lặp for.

  1. Trong main(), bên dưới cuộc gọi gần đây nhất đến println(), hãy thêm một vòng lặp for. Trong dấu ngoặc đơn, hãy đặt tên cho biến planet và lặp qua danh sách solarSystem.
for (planet in solarSystem) {
}
  1. Trong dấu ngoặc nhọn, hãy in giá trị của planet bằng println().
for (planet in solarSystem) {
    println(planet)
}
  1. Chạy mã. Mã trong phần nội dung của vòng lặp được thực thi cho từng mục trong bộ sưu tập.
...
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune

Thêm phần tử vào danh sách

Khả năng thêm, xóa và cập nhật các phần tử trong một bộ sưu tập chỉ dành cho các lớp triển khai giao diện MutableList. Nếu đang theo dõi các hành tinh mới được phát hiện, bạn có thể sẽ muốn thêm được các phần tử vào danh sách một cách thường xuyên. Khi cần tạo danh sách mà bạn muốn thêm và xóa các phần tử, bạn phải gọi hàm mutableListOf() cụ thể thay vì listOf().

Có hai phiên bản của hàm add():

  • Hàm add() đầu tiên có một tham số thuộc loại phần tử trong danh sách và thêm tham số đó vào cuối danh sách.
  • Phiên bản khác của add() có hai tham số. Tham số đầu tiên tương ứng với một chỉ mục mà phần tử mới sẽ được chèn vào đó. Tham số thứ hai là phần tử đang được thêm vào danh sách.

Vui lòng xem ví dụ thực tế.

  1. Thay đổi cách khởi chạy solarSystem để gọi mutableListOf() thay vì listOf(). Giờ đây, bạn có thể gọi các phương thức được xác định trong MutableList.
val solarSystem = mutableListOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
  1. Chúng ta có thể lại muốn phân loại Pluto là một hành tinh. Gọi phương thức add() trên solarSystem, truyền "Pluto" vào để làm đối số duy nhất.
solarSystem.add("Pluto")
  1. Một số nhà khoa học đưa ra giả thuyết về một hành tinh có tên Theia từng tồn tại trước khi va chạm với Trái đất và hình thành Mặt trăng. Chèn "Theia" ở chỉ mục 3, giữa "Earth""Mars".
solarSystem.add(3, "Theia")

Cập nhật các thành phần tại một chỉ mục cụ thể

Bạn có thể cập nhật các phần tử hiện có bằng cú pháp chỉ số dưới:

  1. Cập nhật giá trị tại chỉ mục 3 thành "Future Moon".
solarSystem[3] = "Future Moon"
  1. In giá trị tại chỉ mục 39 bằng cú pháp chỉ số dưới.
println(solarSystem[3])
println(solarSystem[9])
  1. Chạy mã của bạn để xác minh kết quả.
Future Moon
Pluto

Xóa phần tử khỏi danh sách

Các phần tử sẽ bị xóa bằng phương thức remove() hoặc removeAt(). Bạn có thể xóa một phần tử bằng cách truyền phần tử đó vào phương thức remove() hoặc bằng chỉ mục của phần tử đó bằng cách sử dụng removeAt().

Hãy xem cả hai phương pháp xóa phần tử trong thực tế.

  1. Gọi removeAt() trên solarSystem, truyền 9 vào chỉ mục. Thao tác này sẽ xóa "Pluto" khỏi danh sách.
solarSystem.removeAt(9)
  1. Gọi remove() trên solarSystem, truyền "Future Moon" vào dưới dạng phần tử để xóa. Phương thức này sẽ tìm kiếm trong danh sách và nếu tìm thấy phần tử phù hợp, phần tử này sẽ bị xóa.
solarSystem.remove("Future Moon")
  1. List cung cấp phương thức contains() trả về Boolean nếu một phần tử tồn tại trong danh sách. In kết quả gọi contains() cho "Pluto".
println(solarSystem.contains("Pluto"))
  1. Còn một cú pháp ngắn gọn hơn nữa là sử dụng toán tử in. Bạn có thể kiểm tra xem một phần tử có trong danh sách hay không bằng cách sử dụng phần tử, toán tử in và bộ sưu tập. Dùng toán tử in để kiểm tra xem solarSystem có chứa "Future Moon" không.
println("Future Moon" in solarSystem)
  1. Chạy mã. Cả hai câu lệnh đều phải in false.
...
false
false

4. Tập hợp

Tập hợp là một bộ sưu tập không có thứ tự cụ thể và không chấp nhận các giá trị trùng lặp.

ce127adf37662aa4.png

Làm thế nào để tạo một bộ sưu tập tương tự như thế này? Mã băm là một bí . Mã băm là một Int do phương thức hashCode() của bất kỳ lớp Kotlin nào tạo ra. Bạn có thể coi mã này là giá trị nhận dạng gần như duy nhất cho một đối tượng Kotlin. Một thay đổi nhỏ đối với đối tượng, chẳng hạn như việc thêm ký tự vào String sẽ dẫn đến một giá trị băm khác biệt lớn. Mặc dù hai đối tượng có thể có cùng một mã băm (được gọi là xung đột hàm băm), nhưng hàm hashCode() đảm bảo mức độ riêng biệt nào đó trong hầu hết các trường hợp, nơi hai giá trị khác nhau, mỗi giá trị có một mã băm duy nhất.

84842b78e78f2f58.png

Tập hợp có hai thuộc tính quan trọng:

  1. Việc tìm kiếm một phần tử cụ thể trong tập hợp diễn ra nhanh chóng so với danh sách – đặc biệt là đối với các bộ sưu tập lớn. Mặc dù indexOf() của List yêu cầu kiểm tra từng phần tử ban đầu cho đến khi tìm thấy kết quả trùng khớp, nhưng tựu chung cũng mất khoảng thời gian tương tự để kiểm tra xem một phần tử có nằm trong tập hợp hay không, cho dù đó có là phần tử đầu tiên hay phần tử thứ trăm nghìn.
  2. Tập hợp có xu hướng sử dụng nhiều bộ nhớ hơn so với danh sách cho cùng một lượng dữ liệu, vì tập hợp thường cần nhiều chỉ mục mảng hơn là dữ liệu.

Lợi ích của tập hợp là đảm bảo tính duy nhất. Nếu bạn đang viết một chương trình để theo dõi các hành tinh mới được khám phá, thì tập hợp sẽ cung cấp cách đơn giản để kiểm tra xem một hành tinh đã được khám phá hay chưa. Với một lượng lớn dữ liệu, tốt hơn là bạn nên kiểm tra xem phần tử có tồn tại trong danh sách hay không, điều này đòi hỏi việc sử dụng vòng lặp qua tất cả các phần tử.

Giống như ListMutableList, có cả SetMutableSet. MutableSet triển khai Set, vì vậy, bất kỳ lớp nào triển khai MutableSet cũng đều cần triển khai cả hai.

691f995fde47f1ff.png

Sử dụng MutableSet trong Kotlin

Chúng ta sẽ dùng MutableSet trong ví dụ để minh họa cách thêm và xóa các phần tử.

  1. Xóa mã hiện có khỏi main().
  2. Tạo Set hành tinh có tên là solarSystem bằng mutableSetOf(). URL này trả về MutableSet, cách triển khai mặc định là LinkedHashSet().
val solarSystem = mutableSetOf("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune")
  1. In kích thước của tập hợp bằng cách sử dụng thuộc tính size.
println(solarSystem.size)
  1. Giống như List, Set có một phương thức add(). Thêm "Pluto" vào tập hợp solarSystem bằng phương thức add(). Chỉ cần một tham số duy nhất cho phần tử đang được thêm. Các phần tử trong tập hợp không nhất thiết phải có thứ tự, do đó không có chỉ mục!
solarSystem.add("Pluto")
  1. In size của tập hợp sau khi thêm phần tử.
println(solarSystem.size)
  1. Hàm contains() nhận một tham số duy nhất và kiểm tra xem liệu phần tử được chỉ định có nằm trong tập hợp hay không. Nếu có, kết quả sẽ trả về là true. Nếu không, hàm này sẽ trả về giá trị "false". Gọi contains() để kiểm tra xem "Pluto" có trong solarSystem không.
println(solarSystem.contains("Pluto"))
  1. Chạy mã. Kích thước đã tăng và contains() hiện trả về true.
8
9
true
  1. Như đã đề cập trước đó, các tập hợp không được chứa bản sao. Hãy thử thêm lại "Pluto".
solarSystem.add("Pluto")
  1. In lại kích thước của tập hợp.
println(solarSystem.size)
  1. Chạy lại mã. "Pluto" không được thêm vào vì đã có trong tập hợp. Lần này, bạn không phải tăng kích thước.
...
9

Hàm remove() nhận một tham số duy nhất và xóa phần tử được chỉ định khỏi tập hợp.

  1. Dùng hàm remove() để xóa "Pluto".
solarSystem.remove("Pluto")
  1. In kích thước của bộ sưu tập và gọi lại contains() để kiểm tra xem "Pluto" có còn trong tập hợp hay không.
println(solarSystem.size)
println(solarSystem.contains("Pluto"))
  1. Chạy mã. "Pluto" không còn nằm trong tập hợp và kích thước hiện tại là 8.
...
8
false

5. Thu thập bản đồ

Map là một tập hợp bao gồm các khóa và giá trị. Đây được gọi là bản đồ vì các khóa duy nhất được ánh xạ với các giá trị khác. Khóa và giá trị đi kèm thường được gọi là key-value pair.

8571494fb4a106b6.png

Các khóa của bản đồ có giá trị duy nhất. Tuy nhiên, giá trị của bản đồ thì không. Hai khóa khác nhau có thể ánh xạ đến cùng một giá trị. Ví dụ: "Mercury"0 mặt trăng và "Venus"0 mặt trăng.

Thông thường, tốc độ truy cập giá trị từ một bản đồ bằng khóa của bản đồ đó nhanh hơn so với việc tìm kiếm thông qua một danh sách lớn, chẳng hạn như bằng indexOf().

Bạn có thể khai báo Maps bằng hàm mapOf() hoặc mutableMapOf(). Maps yêu cầu hai loại chung chung được phân tách bằng dấu phẩy – một loại cho khóa và một loại cho giá trị.

affc23a0e1f2b223.png

Một bản đồ cũng có thể sử dụng dự đoán loại nếu bản đồ có các giá trị ban đầu. Để điền một bản đồ có các giá trị ban đầu, mỗi cặp giá trị khóa bao gồm khóa, theo sau là toán tử to, tiếp đến là giá trị. Mỗi cặp được phân tách bằng dấu phẩy.

8719ffc353f652f.png

Hãy cùng tìm hiểu cách sử dụng bản đồ cũng như một số thuộc tính và phương pháp hữu ích.

  1. Xóa mã hiện có khỏi main().
  2. Tạo bản đồ có tên là solarSystem bằng cách sử dụng mutableMapOf() với các giá trị ban đầu như được hiển thị.
val solarSystem = mutableMapOf(
    "Mercury" to 0,
    "Venus" to 0,
    "Earth" to 1,
    "Mars" to 2,
    "Jupiter" to 79,
    "Saturn" to 82,
    "Uranus" to 27,
    "Neptune" to 14
)
  1. Giống như danh sách và tập hợp, Map cung cấp thuộc tính size, chứa số lượng cặp khóa-giá trị. In kích thước của bản đồ solarSystem.
println(solarSystem.size)
  1. Bạn có thể sử dụng cú pháp chỉ số dưới để đặt các cặp khóa-giá trị bổ sung. Đặt khóa "Pluto" thành giá trị 5.
solarSystem["Pluto"] = 5
  1. In lại kích thước sau khi chèn phần tử.
println(solarSystem.size)
  1. Bạn có thể sử dụng cú pháp chỉ số dưới để nhận giá trị. In số mặt trăng cho khóa "Pluto".
println(solarSystem["Pluto"])
  1. Bạn cũng có thể truy cập các giá trị bằng phương thức get(). Cho dù bạn sử dụng cú pháp chỉ số dưới hay gọi get(), thì có thể khóa bạn truyền không có trong bản đồ. Nếu không có cặp khóa-giá trị thì hàm sẽ trả về giá trị null. In số mặt trăng cho "Theia".
println(solarSystem["Theia"])
  1. Chạy mã. Số mặt trăng của Pluto sẽ được in. Tuy nhiên, vì Theia không có trong bản đồ, nên việc gọi get() sẽ trả về giá trị null.
8
9
5
null

Phương thức remove() sẽ xóa cặp khóa-giá trị bằng khóa được chỉ định. Thao tác này cũng trả về giá trị đã xóa, hoặc null, nếu khóa được chỉ định không có trong bản đồ.

  1. In kết quả bằng cách gọi remove() và truyền vào "Pluto".
solarSystem.remove("Pluto")
  1. Để xác minh mục đã bị xóa, hãy in lại kích thước.
println(solarSystem.size)
  1. Chạy mã. Kích thước của bản đồ là 8 sau khi xóa mục nhập.
...
8
  1. Cú pháp chỉ số dưới hoặc phương thức put() cũng có thể sửa đổi giá trị cho khóa đã tồn tại. Sử dụng cú pháp chỉ số dưới để cập nhật các mặt trăng của sao Mộc thành 78 và in giá trị mới.
solarSystem["Jupiter"] = 78
println(solarSystem["Jupiter"])
  1. Chạy mã. Giá trị của khóa hiện tại "Jupiter" đã được cập nhật.
...
78

6. Kết luận

Xin chúc mừng! Bạn đã tìm hiểu về một trong các loại dữ liệu cơ bản nhất trong lập trình, mảng và một số loại bộ sưu tập tiện lợi được xây dựng từ các mảng, bao gồm List, SetMap. Các loại bộ sưu tập này cho phép bạn nhóm và sắp xếp giá trị trong mã của mình. Mảng và danh sách cho phép bạn truy cập nhanh vào các phần tử theo chỉ mục của chúng, trong khi tập hợp và bản đồ sử dụng mã băm để giúp bạn dễ dàng tìm thấy các phần tử trong bộ sưu tập hơn. Bạn sẽ thấy các loại bộ sưu tập này được sử dụng thường xuyên ở các ứng dụng trong tương lai, đồng thời việc biết cách sử dụng những bộ sưu tập này sẽ mang lại lợi ích cho bạn trong sự nghiệp lập trình sau này của mình.

Tóm tắt

  • Mảng lưu trữ dữ liệu được sắp xếp theo cùng một loại và có kích thước cố định.
  • Mảng được dùng để triển khai nhiều loại bộ sưu tập khác.
  • Danh sách là một bộ sưu tập có thứ tự và thay đổi kích thước.
  • Tập hợp là bộ sưu tập không theo thứ tự và không thể chứa các bản sao.
  • Maps hoạt động tương tự như tập hợp đồng thời lưu trữ các cặp khóa và giá trị thuộc loại đã chỉ định.

7. Tìm hiểu thêm