Phần 2: Khám phá Các Cấu trúc Dữ liệu Tích hợp (Built-in)

Đây là trái tim của bài viết, nơi chúng ta sẽ đi sâu vào bốn cấu trúc dữ liệu cơ bản nhưng vô cùng mạnh mẽ mà Python cung cấp sẵn. Việc hiểu rõ đặc điểm, ưu và nhược điểm của từng loại sẽ giúp bạn lựa chọn đúng công cụ cho đúng công việc.

2.1. List: Mảng Động Linh Hoạt và Mạnh Mẽ

Định nghĩa

List là một trong những cấu trúc dữ liệu được sử dụng nhiều nhất trong Python. Nó là một bộ sưu tập các phần tử có thứ tự (ordered) và có thể thay đổi (mutable).  

  • Có thứ tự: Các phần tử duy trì một vị trí cụ thể trong list. Vị trí này sẽ không thay đổi trừ khi bạn thêm hoặc xóa phần tử.
  • Có thể thay đổi: Bạn có thể thêm, xóa hoặc thay đổi các phần tử trong list sau khi nó đã được tạo.
  • Linh hoạt: List có thể chứa các phần tử thuộc nhiều kiểu dữ liệu khác nhau trong cùng một list, ví dụ như số nguyên, chuỗi, và thậm chí cả các list khác.  

Cú pháp khởi tạo

List được tạo ra bằng cách đặt các phần tử bên trong cặp dấu ngoặc vuông “, phân tách nhau bởi dấu phẩy.  

Thao tác cơ bản

  • Truy cập phần tử: Bạn có thể truy cập bất kỳ phần tử nào bằng cách sử dụng chỉ số (index) của nó, bắt đầu từ 0 cho phần tử đầu tiên.

Thêm phần tử:

  • append(item): Thêm một phần tử vào cuối list.  
  • insert(index, item): Chèn một phần tử vào vị trí chỉ định.  
  • extend(iterable): Nối tất cả các phần tử từ một iterable (như một list khác) vào cuối list hiện tại.  

Xóa phần tử:

  • remove(item): Xóa phần tử đầu tiên trong list có giá trị khớp với item.  
  • pop(index): Xóa và trả về phần tử tại chỉ số index. Nếu không có index, nó sẽ xóa và trả về phần tử cuối cùng.  
  • del my_list[index]: Xóa phần tử tại một chỉ số cụ thể.
  • clear(): Xóa tất cả các phần tử khỏi list.  
  • Các phương thức hữu ích khác:
    • len(my_list): Trả về số lượng phần tử trong list.  
    • my_list.count(item): Đếm số lần item xuất hiện trong list.  
    • my_list.sort(): Sắp xếp các phần tử của list (thay đổi list gốc).  
    • my_list.reverse(): Đảo ngược thứ tự các phần tử (thay đổi list gốc).  

List Comprehension: Cú pháp Pythonic

List comprehension cung cấp một cách ngắn gọn và dễ đọc hơn để tạo list từ các iterable khác, thường thay thế cho các vòng lặp for cồng kềnh.  

Cú pháp chung: [expression for item in iterable if condition]

Khi nào nên dùng List?

List là lựa chọn mặc định cho nhiều tình huống. Hãy sử dụng List khi:

  • Bạn cần một bộ sưu tập dữ liệu có thể thay đổi thường xuyên (thêm, xóa, sửa đổi).  
  • Thứ tự của các phần tử là quan trọng và cần được duy trì.
  • Bạn cần lưu trữ các phần tử có thể trùng lặp.  

Ví dụ thực tế: danh sách các mặt hàng trong giỏ hàng của một trang web thương mại điện tử, danh sách sinh viên trong một lớp học, một chuỗi các bước cần thực hiện trong một quy trình.

Phân tích sâu: Chi phí “ẩn” của sự linh hoạt

Sự linh hoạt của List không phải là miễn phí. Mặc dù các thao tác như append() (thêm vào cuối) và pop() (xóa ở cuối) thường rất nhanh, với độ phức tạp thời gian trung bình là O(1), các thao tác ở đầu list lại rất tốn kém.  

Bên trong, Python triển khai List dưới dạng một mảng động. Khi bạn thực hiện list.pop(0) hoặc list.insert(0, item), tất cả các phần tử còn lại trong list phải được dịch chuyển sang trái hoặc phải một vị trí. Đây là một thao tác có độ phức tạp O(n), nghĩa là thời gian thực hiện sẽ tăng tuyến tính với số lượng phần tử trong list. Với một list chứa hàng triệu phần tử, việc này có thể trở nên cực kỳ chậm chạp.

Điều này có nghĩa là việc sử dụng List để triển khai một cấu trúc dữ liệu như hàng đợi (Queue), nơi các phần tử được thêm vào một đầu và xóa ở đầu kia (First-In, First-Out), là một lựa chọn thiết kế không tối ưu về mặt hiệu suất. Đây chính là lý do tại sao module collections của Python cung cấp một giải pháp thay thế hiệu quả hơn là deque, mà chúng ta sẽ tìm hiểu ở phần sau.

2.2. Tuple: Bộ Sưu Tập Bất Biến và Hiệu Quả

Định nghĩa

Tuple (phát âm là “túp-pồ” hoặc “táp-pồ”) rất giống với List, nhưng có một khác biệt cốt lõi: nó bất biến (immutable). Điều này có nghĩa là một khi một tuple được tạo, bạn không thể thêm, xóa, hoặc thay đổi các phần tử của nó. Giống như List, Tuple cũng là một bộ sưu tập  có thứ tự.  

Cú pháp khởi tạo

Tuple được tạo ra bằng cách sử dụng dấu ngoặc tròn () hoặc đơn giản là các giá trị được phân tách bằng dấu phẩy.  

Thao tác

Vì là bất biến, các phương thức làm thay đổi cấu trúc như append(), remove(), sort() đều không tồn tại trên Tuple. Các thao tác chính bạn có thể thực hiện là:  

  • Truy cập phần tử: Bằng chỉ số, giống hệt như List. toa_do sẽ trả về 10.
  • Slicing: Lấy một phần của tuple, tạo ra một tuple mới.
  • Các phương thức: count(item)index(item).

Khi nào nên dùng Tuple?

  • Đảm bảo tính toàn vẹn dữ liệu (Data Integrity): Khi bạn có một bộ dữ liệu không bao giờ nên thay đổi trong suốt vòng đời của chương trình, việc sử dụng Tuple sẽ giúp ngăn chặn các thay đổi vô tình. Ví dụ: tọa độ điểm (x, y), thông tin màu sắc RGB, các hằng số cấu hình.  
  • Trả về nhiều giá trị từ một hàm: Đây là một quy ước rất phổ biến trong Python. Thay vì trả về một đối tượng phức tạp, các hàm thường trả về một tuple chứa nhiều kết quả.
  • Sử dụng làm khóa (key) cho Dictionary hoặc phần tử cho Set: Vì Tuple là bất biến, nó có thể được “băm” (hashable). Điều này cho phép nó được sử dụng làm khóa trong Dictionary hoặc phần tử trong Set, trong khi List (là mutable) thì không thể.  

Phân tích sâu: Tuple không chỉ là “List không thể thay đổi”

Nhiều người mới học thường chỉ coi Tuple là một phiên bản “chỉ đọc” (write-protect) của List. Tuy nhiên, một góc nhìn sâu sắc hơn trong cộng đồng Python là về  

ý nghĩa ngữ nghĩa (semantic meaning).  

  • List thường được sử dụng cho các bộ sưu tập đồng nhất (homogeneous) – một chuỗi các đối tượng cùng loại, có độ dài thay đổi. Ví dụ: một danh sách các tên sinh viên, một danh sách các giá sản phẩm.
  • Tuple thường được sử dụng cho các bộ sưu tập không đồng nhất (heterogeneous) – nơi mỗi vị trí có một ý nghĩa riêng và toàn bộ các phần tử cùng nhau tạo thành một thực thể, một bản ghi duy nhất. Ví dụ: ('Nguyễn Văn A', 25, 'Lập trình viên') là một bản ghi về nhân viên, nơi vị trí đầu tiên luôn là tên, thứ hai là tuổi, và thứ ba là nghề nghiệp.

Việc hiểu sự khác biệt về ngữ nghĩa này giúp bạn viết code rõ ràng và tự biểu đạt hơn. Khi một lập trình viên khác đọc code của bạn và thấy một Tuple, họ có thể ngầm hiểu rằng đây là một cấu trúc dữ liệu cố định, một bản ghi có cấu trúc. Ngược lại, khi họ thấy một List, họ sẽ hiểu đây là một chuỗi các mục có thể thay đổi.

2.3. So sánh “Kinh điển”: List vs. Tuple

Sự lựa chọn giữa List và Tuple là một trong những quyết định phổ biến nhất mà lập trình viên Python phải đối mặt. Sự khác biệt cốt lõi giữa chúng bắt nguồn từ một khái niệm duy nhất: Tính thay đổi (Mutability).  


Hiệu suất và Bộ nhớ

  • Bộ nhớ: Do có kích thước cố định, Tuple thường chiếm ít bộ nhớ hơn List. Python không cần phải cấp phát thêm bộ nhớ dự phòng cho Tuple trong trường hợp nó cần mở rộng, vì nó không thể mở rộng.  
  • Tốc độ: Tuple được khởi tạo nhanh hơn một chút so với List. Việc truy cập các phần tử cũng có thể nhanh hơn một chút.  

Tuy nhiên, cần nhấn mạnh rằng sự khác biệt về hiệu suất này thường rất nhỏ và không đáng kể trong hầu hết các ứng dụng thực tế. Tính đúng đắn, rõ ràng và an toàn của code (ví dụ: sử dụng Tuple để bảo vệ dữ liệu không đổi) thường quan trọng hơn là việc tối ưu hóa vi mô này.  

Khả năng “Băm” (Hashability)

Đây là hệ quả trực tiếp và quan trọng nhất của tính bất biến. Một đối tượng được gọi là “hashable” nếu nó có một giá trị hash không bao giờ thay đổi trong suốt vòng đời của nó. Vì Tuple là bất biến, nó là hashable. Ngược lại, vì List có thể thay đổi, giá trị hash của nó cũng có thể thay đổi, do đó nó không phải là hashable.

Điều này giải thích tại sao bạn có thể sử dụng một Tuple (chứa các phần tử hashable) làm khóa cho Dictionary, nhưng không thể sử dụng List.  

Bảng so sánh chi tiết

Để giúp bạn đưa ra quyết định, đây là bảng so sánh chi tiết giữa List và Tuple:

2.4. Dictionary: Sức mạnh của cặp Key-Value

Định nghĩa

Dictionary (hay dict) là một cấu trúc dữ liệu cực kỳ mạnh mẽ và linh hoạt, cho phép lưu trữ dữ liệu dưới dạng các cặp khóa-giá trị (key-value). Thay vì truy cập dữ liệu bằng một chỉ số số học như List hay Tuple, bạn truy cập nó thông qua một “khóa” duy nhất. Nó giống như một cuốn từ điển thực tế, nơi bạn tra một từ (key) để tìm định nghĩa của nó (value).  

Đặc điểm quan trọng

  • Có thể thay đổi (Mutable): Bạn có thể thêm, xóa và sửa đổi các cặp key-value sau khi dictionary được tạo.  
  • Khóa là duy nhất và bất biến: Mỗi khóa trong một dictionary phải là duy nhất. Nếu bạn cố gắng thêm một khóa đã tồn tại, giá trị cũ sẽ bị ghi đè. Các khóa phải là các đối tượng hashable (bất biến) như số, chuỗi, hoặc tuple.  
  • Giá trị đa dạng: Giá trị (value) có thể là bất kỳ đối tượng Python nào: số, chuỗi, list, tuple, hoặc thậm chí là một dictionary khác.  
  • Có thứ tự (từ Python 3.7): Đây là một cập nhật quan trọng. Trong các phiên bản Python cũ hơn (trước 3.6), dictionary được coi là không có thứ tự. Tuy nhiên, từ phiên bản 3.7, ngôn ngữ Python chính thức đảm bảo rằng dictionary sẽ duy trì thứ tự các phần tử được thêm vào.  

Cú pháp và Thao tác

  • Khởi tạo: Sử dụng cặp dấu ngoặc nhọn {} hoặc hàm dict().  

Truy cập, Thêm và Sửa đổi: Sử dụng cú pháp my_dict['key']. Nếu key đã tồn tại, giá trị sẽ được cập nhật. Nếu không, một cặp key-value mới sẽ được tạo.  

Xóa: Sử dụng từ khóa del hoặc phương thức pop().

  • del person['thanh_pho']: Xóa cặp key-value có khóa là ‘thanh_pho’.
  • tuoi = person.pop('tuoi'): Xóa cặp có khóa là ‘tuoi’ và trả về giá trị của nó.

Các phương thức quan trọng

  • keys(): Trả về một đối tượng view chứa tất cả các khóa của dictionary.  
  • values(): Trả về một đối tượng view chứa tất cả các giá trị.  
  • items(): Trả về một đối tượng view chứa các cặp (key, value) dưới dạng tuple. Đây là cách hiệu quả nhất để lặp qua cả khóa và giá trị cùng lúc.  
  • get(key, default=None): Một cách truy cập an toàn. Nếu key tồn tại, nó trả về giá trị tương ứng. Nếu không, nó trả về giá trị default (mặc định là None) thay vì gây ra lỗi KeyError.  

Khi nào nên dùng Dictionary?

Hãy sử dụng Dictionary khi:

  • Bạn cần tạo một mối liên kết logic giữa các cặp dữ liệu (key-value).  
  • Bạn cần truy xuất dữ liệu nhanh chóng thông qua một định danh duy nhất (khóa) thay vì vị trí.  
  • Thứ tự dữ liệu không phải là ưu tiên hàng đầu (mặc dù nó đã được đảm bảo từ Python 3.7).

Ví dụ thực tế: lưu trữ hồ sơ người dùng (key là ID người dùng, value là một dictionary khác chứa thông tin chi tiết), đếm tần suất xuất hiện của các từ trong một văn bản, lưu trữ các thiết lập cấu hình cho một ứng dụng.

Phân tích sâu: Dictionary là “trái tim” của Python

Dictionary không chỉ là một cấu trúc dữ liệu tiện lợi mà bạn có thể sử dụng; nó là một phần cơ bản và được tối ưu hóa cao của chính ngôn ngữ Python. Một sự thật thú vị là Python sử dụng dictionary cho chính việc triển khai nội bộ của các đối tượng và lớp thông qua một thuộc tính đặc biệt gọi là __dict__.  

Khi bạn tạo một đối tượng và truy cập một thuộc tính, ví dụ my_object.attribute, về cơ bản Python đang thực hiện một phép tra cứu trong dictionary my_object.__dict__.

Điều này cho thấy hiệu suất tra cứu gần như tức thời (O(1)) của dictionary là nền tảng cho hiệu suất của việc truy cập thuộc tính trong lập trình hướng đối tượng của Python. Hiểu được điều này giúp chúng ta đánh giá cao hơn sức mạnh và tầm quan trọng của dictionary, không chỉ như một công cụ mà còn là một phần cốt lõi của kiến trúc ngôn ngữ.

Share this article

Leave a Reply

Your email address will not be published. Required fields are marked *

Nhận thông báo

Bằng cách nhấn nút Đăng ký, bạn xác nhận rằng bạn đã đọc Chính sách bảo mật của chúng tôi.​