Dùng tập hợp trong Kotlin

1. Giới thiệu

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

46df844b170f4272.png

Tuy nhiên đến thời điểm hiện tại, bạn chủ yếu viết mã với các kiểu dữ liệu chứa một giá trị, như một số hoặc một đoạn văn bản xuất hiện trên màn hình. Để tạo ứng dụng liên quan đến lượng dữ liệu tuỳ ý, bạn cần tìm hiểu cách sử dụng collection.

Các kiểu tập hợp (còn đượ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 kiểu dữ liệu) theo cấu trúc nhất định. Một tập hợp có thể là một danh sách theo thứ tự, một nhóm các giá trị không trùng lặp hoặc một tệp ánh xạ các giá trị từ kiểu dữ liệu này đến các giá trị của kiểu dữ liệu khác. Khả năng sử dụng tập hợp hiệu quả 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ư cuộn danh sách, hoặc giải quyết các vấn đề thực tế trong lập trình liên quan đến lượng dữ liệu tuỳ ý.

Lớp học lập trình này thảo luận 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 các cấu trúc dữ liệu khác nhau, bao gồm mảng, danh sách, tập hợp và tệp ánh xạ.

Đ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ị tuỳ ý vào 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à chuỗi các giá trị mà tất cả đều có cùng một loại dữ liệu.

33986e4256650b8b.png

  • Các giá trị trong một mảng được gọi là phần tử hoặc đôi khi là 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 nằm ở chỉ mục 1, bởi phần tử này ở vị trí thứ hai sau phần tử đầu tiên, và cứ tiếp tục như thế.

bb77ec7506ac1a26.png

Trong bộ nhớ của thiết bị, các phần tử trong mảng được lưu trữ liên tiếp 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:

  • Bạn có thể nhanh chóng truy cập vào một phần tử mảng qua chỉ mục của chúng. Bạn có thể 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 chúng và sẽ mất cùng một khoảng thời gian để truy cập một phần tử ngẫu nhiên 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ượt quá kích thước mảng đã khai báo. 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ệ (exception) vì chỉ mục cao nhất là 99 (lưu ý chỉ mục đầu tiên là 0, không phải 1). Tuy nhiên, bạn có thể sửa đổi giá trị của các chỉ mục trong mảng.

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

69e283b32d35f799.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 số lượng tham số khác nhau. Nếu bạn truyền 2 đối số vào arrayOf(), kết quả sẽ trả về một mảng chứa 2 phần tử, có chỉ mục là 0 và 1. Nếu bạn truyền 3 đố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ách các mảng hoạt động qua ví dụ nhỏ sau về hệ mặt trời!

  1. Chuyển đế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 đất đá trong hệ mặt trời.
val rockPlanets = arrayOf<String>("Mercury", "Venus", "Earth", "Mars")
  1. Vì Kotlin sử dụng cách suy luận theo kiểu, nên bạn có thể bỏ qua tên kiểu khi gọi arrayOf(). Bên dưới biến rockPlanets, hãy thêm biến gasPlanets mà không 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á. Chẳng hạn, tương tự như các kiểu dữ liệu số Int hoặc Double, bạn có thể cộng 2 mảng với nhau. Tạo một biến mới có tên là solarSystem để gán kết quả nối hai mảng rockPlanetsgasPlanets bằng toán tử cộng (+). Kết quả trả về 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 để đảm bảo nó hoạt động đúng cách. Bạn sẽ 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 lên màn hình. 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ử hiển thị 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ên — nghĩa là tên của mảng, tiếp đến là chỉ mục đặt trong dấu ngoặc vuông. 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ị trong mảng solarSystem.

  1. Hãy đặt một tên mới cho sao Hoả nhằm 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 một hành tinh thứ 9 nằm bên ngoài sao Hải Vương với tên gọi sao Diêm Vương. 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 (sao Diêm Vương) vào mảng solarSystem. Hãy thêm Pluto (Sao Diêm Vương) vào chỉ mục 8 vì đây là phần tử thứ 9 của 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ệ (exception) ArrayIndexOutOfBounds. Vì mảng đã có sẵn 8 phần tử, đúng như dự kiến, bạn không thể thêm phần tử thứ chín.
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 8 out of bounds for length 8
  1. Xoá Pluto đã thêm vào mảng.

Đoạn mã cần xoá

solarSystem[8] = "Pluto"
  1. Để tăng kích thước của mảng, 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 hoạ. 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ể thoả 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 với các tác vụ yêu cầu thêm và xoá các phần tử, áp dụng tính duy nhất trong một tập hợp hoặc ánh xạ một đối tượng đến các đối tượng khác không hề đơn giản, mã nguồn 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 kiểu tập hợ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 bạn thêm vào một phần tử làm vượt quá kích thước của mảng, các phần tử trong 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 những phần tử khác trong một chỉ mục cụ thể.

a678d6a41e6afd46.png

Đây là cách các danh sách có thể thêm và xoá 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. Trong một số trường hợp việc thêm vào một phần tử làm vượt quá kích thước đã khai báo của mảng, vị trí các phần tử trong mảng có thể thay đổi để tạo chỗ trống cho các phần tử mới. 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à hoán đổi một mảng cho một mảng mới nếu cần thiết.

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 loại chung, đối tượng và tiện ích, giao diện cung cấp một tập hợp đầy đủ các thuộc tính và phương thức cho 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 mọi 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 có thứ tự, ở chế độ chỉ đọc.
  • 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à xoá phần tử.

Các giao diện này chỉ xác định các thuộc tính và phương thức của List và/hoặc MutableList. Việc xác định cách triển khai từng thuộc tính và phương thức sẽ phụ thuộc vào lớp đã mở rộng các giao diện đó. 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. Xoá 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 một phần tử có chỉ mục cụ thể trong 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, chỉ mục trong ArrayList cũng bắt đầu từ 0. Chẳng hạn, phần tử thứ tư sẽ nằm trong chỉ mục 3.

  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 hàm indexOf(), truyền vào giá trị "Pluto" rồi in kết quả lên màn hình.
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 sẽ 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 lặp lại qua một danh sách.

f11277e6af4459bb.png

Để lặp lại theo một danh sách, hãy dùng từ khoá for, tiếp đến là một cặp dấu ngoặc đơn. Trong cặp dấu ngoặc đơn, hãy thêm tên biến, rồi đến từ khoá in, tiếp đó là tên tập hợp. Sau cặp dấu ngoặc đơn sẽ là một cặp dấu ngoặc nhọn, trong đó bạn phải thêm mã mình muốn thực thi cho từng phần tử trong tập hợ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ừ khoá in bằng val hoặc var – những biến này đượ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 của tập hợ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 trong cặp 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, xoá và cập nhật các phần tử trong một tập hợ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à xoá các phần tử, bạn phải gọi chính xác hàm mutableListOf(), 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ử cần thêm vào danh sách.

Hãy 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 lên màn hình 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

Xoá phần tử khỏi danh sách

Các phần tử sẽ bị xoá bằng phương thức remove() hoặc removeAt(). Bạn có thể xoá 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 xoá 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ẽ xoá "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ử để xoá. 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ị xoá.
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.

9de9d777e6b1d265.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 một ký tự vào String sẽ dẫn đến việc thay đổi đáng kể giá trị băm. Mặc dù 2 đối tượng có thể có cùng một mã băm (được gọi là một xung đột băm), nhưng hàm hashCode() đảm bảo mức độ riêng biệt nhất định. Cụ thể, trong hầu hết các trường hợp với 2 giá trị khác nhau, mỗi giá trị có 1 mã băm riêng.

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 trung bình cũng mất một khoảng thời gian tương đương để 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 hoạ cách thêm và xoá các phần tử.

  1. Xoá 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 lên màn hình 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ư MutableList, MutableSet 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à xoá phần tử được chỉ định khỏi tập hợp.

  1. Dùng hàm remove() để xoá "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 của tập lúc này là 8.
...
8
false

5. Thu thập bản đồ

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

8571494fb4a106b6.png

Các khoá của bản đồ có giá trị duy nhất. Tuy nhiên, giá trị trong tệp ánh xạ thì không. Hai khoá 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 tệp ánh xạ bằng khoá của tệp ánh xạ đó 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(). Tệp ánh xạ gồm 2 kiểu chung được phân tách bằng dấu phẩy – một kiểu cho khoá và một kiểu 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. Để thêm vào một tệp ánh xạ có sẵn các giá trị ban đầu, cặp khoá-giá trị cần có khoá, tiếp đến là toán tử to, cuối cùng là giá trị. Mỗi cặp được phân tách bằng dấu phẩy.

2ed99c3391c74ec4.png

Hãy cùng tìm hiểu kỹ hơn cách dùng tệp ánh xạ cũng như một số thuộc tính và phương thức hữu ích.

  1. Xoá mã hiện có khỏi main().
  2. Tạo tệp ánh xạ có tên là solarSystem bằng cách sử dụng mutableMapOf() với các giá trị ban đầu như sau.
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 khoá-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 thêm các cặp khoá-giá trị. Đặt khoá "Pluto" thành giá trị 5.
solarSystem["Pluto"] = 5
  1. In lên màn hình 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 khoá "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ể khoá bạn truyền không có trong bản đồ. Nếu không có cặp khoá-giá trị thì hàm sẽ trả về giá trị null. In số mặt trăng cho "Theia".
println(solarSystem.get("Theia"))
  1. Chạy mã. Số mặt trăng của Pluto (Sao Diêm Vương) sẽ được hiển thị. 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ẽ xoá cặp khoá-giá trị bằng khoá được chỉ định. Thao tác này cũng trả về giá trị đã xoá, hoặc null, nếu khoá được chỉ định không có trong tệp ánh xạ.

  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ị xoá, hãy in lại kích thước lên màn hình.
println(solarSystem.size)
  1. Chạy mã. Kích thước của tệp ánh xạ là 8 sau khi xoá 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 khoá đã 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 Jupiter (Sao Mộc) thành 78 và in lên màn hình giá trị mới.
solarSystem["Jupiter"] = 78
println(solarSystem["Jupiter"])
  1. Chạy mã. Giá trị của khoá 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 tập hợp có thứ tự và có thể thay đổi kích thước.
  • Tập hợp là tập các phần tử không theo thứ tự và không thể chứa các bản sao.
  • Tệp ánh xạ hoạt động tương tự như tập hợp đồng thời lưu trữ các cặp khoá và giá trị thuộc kiểu xác định.

7. Tìm hiểu thêm