Bài 13.5: Bài tập thực hành Vector và Pair trong C++

Chào mừng các bạn quay trở lại với series học C++ của chúng ta! Sau khi đã làm quen với các khái niệm cơ bản, hôm nay chúng ta sẽ đi sâu vào thực hành với hai công cụ cực kỳ mạnh mẽphổ biến trong C++ Standard Library (STL): vectorpair.

vectorpair không chỉ là những thành phần cơ bản mà còn là nền tảng để xây dựng nên các cấu trúc dữ liệu và thuật toán phức tạp hơn. Nắm vững cách sử dụng chúng sẽ giúp code của bạn trở nên ngắn gọn, hiệu quảdễ bảo trì hơn rất nhiều.

Hãy cùng bắt tay vào thực hành ngay nhé!

1. Khám Phá vector - Mảng Động Quyền Năng

Trong C++, mảng truyền thống có kích thước cố định khi khai báo. Điều này có thể gây khó khăn khi bạn không biết chính xác số lượng phần tử cần lưu trữ từ đầu, dẫn đến lãng phí bộ nhớ hoặc tràn bộ nhớ (buffer overflow). vector ra đời để giải quyết vấn đề này.

vector là một container (bộ chứa) cung cấp khả năng quản lý một mảng động. Tức là kích thước của nó có thể tự động co giãn trong quá trình chạy chương trình. Điều này vô cùng tiện lợi!

1.1. Khai báo và Thêm phần tử

Bạn có thể khai báo một vector rỗng và thêm phần tử vào sau:

#include <vector>
#include <iostream>

int main() {
    // Khai báo một vector kiểu int rỗng
    vector<int> danhSachSoNguyen;

    // Thêm phần tử vào cuối vector
    danhSachSoNguyen.push_back(10);
    danhSachSoNguyen.push_back(25);
    danhSachSoNguyen.push_back(5);
    danhSachSoNguyen.push_back(42);

    // In kích thước hiện tại của vector
    cout << "Kich thuoc hien tai cua vector: " << danhSachSoNguyen.size() << endl;

    return 0;
}

Giải thích:

  • #include <vector>: Cần thiết để sử dụng vector.
  • vector<int> danhSachSoNguyen;: Khai báo một vector có thể chứa các số nguyên (int). Ban đầu nó rỗng.
  • push_back(): Hàm thành viên này thêm một phần tử mới vào cuối vector. Vector sẽ tự động điều chỉnh kích thước nếu cần.
  • size(): Hàm thành viên này trả về số lượng phần tử hiện có trong vector.
1.2. Truy cập phần tử

Bạn có thể truy cập các phần tử trong vector giống như mảng thông thường, bằng cách sử dụng toán tử [] hoặc hàm at().

#include <vector>
#include <iostream>

int main() {
    vector<string> danhSachTen = {"Alice", "Bob", "Charlie", "David"};

    // Truy cập phần tử bằng toán tử [] (không kiểm tra biên)
    cout << "Phan tu tai chi so 0: " << danhSachTen[0] << endl; // Output: Alice
    cout << "Phan tu tai chi so 2: " << danhSachTen[2] << endl; // Output: Charlie

    // Truy cập phần tử bằng at() (có kiểm tra biên, ném ngoại lệ nếu lỗi)
    try {
        cout << "Phan tu tai chi so 3: " << danhSachTen.at(3) << endl; // Output: David
        // cout << "Phan tu tai chi so 5: " << danhSachTen.at(5) << endl; // Se gay loi ngoai le
    } catch (const out_of_range& e) {
        cerr << "Loi: " << e.what() << endl;
    }


    return 0;
}

Giải thích:

  • danhSachTen[index]: Truy cập phần tử tại chỉ số index. Lưu ý: Nếu index nằm ngoài phạm vi hợp lệ (từ 0 đến size() - 1), hành vi sẽ không xác định (undelined behavior) và có thể gây crash chương trình.
  • danhSachTen.at(index): Tương tự, nhưng nếu index nằm ngoài phạm vi, nó sẽ ném ra một ngoại lệ kiểu out_of_range. Sử dụng at() an toàn hơn khi bạn không chắc chắn về tính hợp lệ của chỉ số.
  • Ở ví dụ này, chúng ta đã khai báo và khởi tạo vector ngay lập tức bằng danh sách khởi tạo {}.
1.3. Duyệt (Iterate) vector

Có nhiều cách để duyệt qua tất cả các phần tử trong vector. Hai cách phổ biến nhất là dùng vòng lặp for truyền thống và vòng lặp for dựa trên phạm vi (range-based for loop).

#include <vector>
#include <iostream>
#include <string> // Can thiet cho string

int main() {
    vector<double> danhSachDiem = {7.5, 8.0, 6.5, 9.0, 7.0};

    // Cach 1: Duyet bang vong lap for truyen thong (su dung chi so)
    cout << "Duyet bang chi so:" << endl;
    for (size_t i = 0; i < danhSachDiem.size(); ++i) {
        cout << "Diem [" << i << "]: " << danhSachDiem[i] << endl;
    }

    // Cach 2: Duyet bang vong lap range-based for (don gian hon)
    cout << "\nDuyet bang range-based for:" << endl;
    for (double diem : danhSachDiem) {
        cout << "Diem: " << diem << endl;
    }

    return 0;
}

Giải thích:

  • Vòng lặp for truyền thống sử dụng một biến chỉ số (i) từ 0 đến size() - 1. Kiểu size_t là kiểu không dấu thường được sử dụng cho kích thước và chỉ số trong C++.
  • Vòng lặp range-based for (có từ C++11) đơn giản hơn nhiều. Nó tự động lặp qua từng phần tử trong danhSachDiem và gán giá trị của phần tử hiện tại vào biến diem. Đây là cách được ưu tiên sử dụng khi bạn chỉ cần đọc hoặc sao chép giá trị của các phần tử. Nếu bạn muốn thay đổi giá trị của phần tử khi duyệt, bạn sẽ cần sử dụng tham chiếu: for (double& diem : danhSachDiem) { diem *= 2; }.

vector còn có nhiều thao tác khác như xóa phần tử (erase, pop_back), chèn phần tử (insert), xóa tất cả phần tử (clear), kiểm tra rỗng (empty), v.v. Nhưng với các thao tác cơ bản trên, bạn đã có thể bắt đầu sử dụng vector một cách hiệu quả rồi!

2. Làm Quen Với pair - Bộ Đôi Hoàn Hảo

Đôi khi, bạn cần lưu trữ hai giá trị có liên quan với nhau như một đơn vị duy nhất. Ví dụ: tọa độ (x, y), tên và tuổi, mã sản phẩm và số lượng, khóa và giá trị trong một dictionary đơn giản. Lúc này, pair là một lựa chọn tuyệt vời.

pair là một struct đơn giản trong STL, dùng để gói gọn hai giá trị thành một đối tượng duy nhất. Hai giá trị này có thể có kiểu dữ liệu khác nhau.

2.1. Khai báo và Khởi tạo pair

Bạn có thể khai báo và khởi tạo pair theo nhiều cách:

#include <utility> // Can thiet cho pair
#include <string>
#include <iostream>

int main() {
    // Cach 1: Khai bao va khoi tao sau
    pair<string, int> thongTinSinhVien;
    thongTinSinhVien.first = "Nguyen Van A";
    thongTinSinhVien.second = 20;

    // Cach 2: Khoi tao ngay luc khai bao (su dung danh sach khoi tao {})
    pair<double, double> toaDo = {3.14, 2.71};

    // Cach 3: Su dung make_pair (thuong dung hon trong mot so truong hop)
    auto maVaTenSP = make_pair("SP001", "Laptop Dell"); // auto tu dong suy luan kieu

    // In cac gia tri
    cout << "Sinh vien: " << thongTinSinhVien.first << ", Tuoi: " << thongTinSinhVien.second << endl;
    cout << "Toa do: (" << toaDo.first << ", " << toaDo.second << ")" << endl;
    cout << "San pham: " << maVaTenSP.first << ", Ten: " << maVaTenSP.second << endl;

    return 0;
}

Giải thích:

  • #include <utility>: Cần thiết để sử dụng pairmake_pair.
  • pair<Type1, Type2> ten_pair;: Khai báo một pair chứa một giá trị kiểu Type1 và một giá trị kiểu Type2.
  • Bạn truy cập hai giá trị bên trong pair thông qua các thành viên có tên cố định: .first.second.
  • make_pair(value1, value2): Một hàm tiện ích tạo ra một pair từ hai giá trị được cung cấp. Khi dùng với auto, trình biên dịch sẽ tự động xác định kiểu của pair.

3. Sức Mạnh Tổng Hợp: vectorpair Kết Hợp

Đây là lúc mọi thứ trở nên thú vị. vector có thể chứa bất kỳ kiểu dữ liệu nào, bao gồm cả pair. Và ngược lại, pair cũng có thể chứa vector. Sự kết hợp này mở ra khả năng tạo ra các cấu trúc dữ liệu linh hoạt và mạnh mẽ.

3.1. vector của pair

Đây là trường hợp rất phổ biến. Bạn muốn lưu trữ một danh sách các cặp dữ liệu.

Ví dụ: Danh sách các điểm thi của sinh viên, mỗi điểm thi là một cặp (Tên môn học, Điểm số).

#include <vector>
#include <utility>
#include <string>
#include <iostream>

int main() {
    // Khai bao vector chua cac pair kieu (string, double)
    vector<pair<string, double>> diemThiSinhVien;

    // Them du lieu vao vector
    diemThiSinhVien.push_back({"Toan", 8.5});
    diemThiSinhVien.push_back(make_pair("Ly", 7.0)); // Co the dung {} hoac make_pair
    diemThiSinhVien.push_back({"Hoa", 9.2});

    // Duyet va in thong tin
    cout << "Bang diem cua sinh vien:" << endl;
    for (const auto& diem : diemThiSinhVien) { // Dung const auto& de hieu qua hon khi duyet
        cout << "- Mon: " << diem.first << ", Diem: " << diem.second << endl;
    }

    return 0;
}

Giải thích:

  • vector<pair<string, double>>: Khai báo một vector mà mỗi phần tử của nó là một pair chứa một string (tên môn) và một double (điểm).
  • Khi thêm vào bằng push_back, bạn có thể truyền trực tiếp một pair được tạo bằng {} hoặc make_pair.
  • Khi duyệt, mỗi biến diem trong vòng lặp range-based for là một pair<string, double>. Chúng ta dùng const auto& để tránh sao chép pair lớn và đảm bảo không vô tình thay đổi giá trị.

Ví dụ khác: Danh sách các điểm trên mặt phẳng 2D.

#include <vector>
#include <utility>
#include <iostream>

int main() {
    // Khai bao vector chua cac pair kieu (double, double) - Toa do (x, y)
    vector<pair<double, double>> danhSachDiem2D;

    // Them cac diem
    danhSachDiem2D.push_back({1.0, 2.5});
    danhSachDiem2D.push_back({-3.0, 0.0});
    danhSachDiem2D.push_back({5.5, -1.2});

    // In danh sach diem
    cout << "Danh sach cac diem 2D:" << endl;
    for (const auto& diem : danhSachDiem2D) {
        cout << "  (" << diem.first << ", " << diem.second << ")" << endl;
    }

    return 0;
}

Giải thích: Tương tự như ví dụ trên, nhưng các pair chứa hai giá trị cùng kiểu double.

3.2. pair chứa vector

Trường hợp này ít gặp hơn trong các bài tập cơ bản nhưng cũng là một khả năng mạnh mẽ. Bạn muốn gói gọn hai thứ, trong đó có một thứ là một danh sách.

Ví dụ: Thông tin của một học sinh bao gồm tên và một danh sách các điểm số.

#include <vector>
#include <utility>
#include <string>
#include <iostream>
#include <numeric> // Can thiet cho accumulate

int main() {
    // Khai bao pair chua (string, vector<int>)
    pair<string, vector<int>> duLieuHocSinh;

    // Gan gia tri
    duLieuHocSinh.first = "Tran Thi B";
    duLieuHocSinh.second = {7, 8, 9, 6, 10}; // Gan mot vector diem

    // Truy cap va xu ly
    cout << "Ten hoc sinh: " << duLieuHocSinh.first << endl;

    cout << "Cac diem: ";
    for (int diem : duLieuHocSinh.second) {
        cout << diem << " ";
    }
    cout << endl;

    // Tinh diem trung binh (su dung accumulate tu <numeric>)
    double tongDiem = accumulate(duLieuHocSinh.second.begin(), duLieuHocSinh.second.end(), 0.0);
    double diemTrungBinh = tongDiem / duLieuHocSinh.second.size();
    cout << "Diem trung binh: " << diemTrungBinh << endl;

    return 0;
}

Giải thích:

  • pair<string, vector<int>>: Khai báo một pair mà phần tử đầu (first) là một string và phần tử thứ hai (second) là một vector chứa các số nguyên (int).
  • Chúng ta truy cập vector bên trong pair thông qua duLieuHocSinh.second và sau đó thực hiện các thao tác trên vector đó như bình thường (duyệt, tính toán...).

4. Một Vài Bài Tập Nhỏ Để Củng Cố

Hãy thử áp dụng những gì đã học vào các bài tập nhỏ sau:

  • Bài tập 1: Tạo một vector lưu trữ 5 số thực do người dùng nhập từ bàn phím. Sau đó, tìm và in ra số lớn nhất trong vector đó.
  • Bài tập 2: Tạo một vector lưu trữ các cặp (tên thành phố, dân số). Nhập dữ liệu cho 3-4 thành phố. In ra tên thành phố có dân số lớn nhất.
  • Bài tập 3: Tạo một pair chứa (Tên đội bóng, Danh sách điểm số trong các trận đấu). Khởi tạo dữ liệu và tính điểm trung bình của đội bóng đó.

Những bài tập này sẽ giúp bạn củng cố kiến thức về cách khai báo, thêm, truy cập và duyệt vector cũng như cách làm việc với các thành phần của pair.

vectorpair là những công cụ không thể thiếu trong hộp đồ nghề của một lập trình viên C++. Việc thực hành thường xuyên với chúng sẽ giúp bạn tự tin hơn khi xử lý các vấn đề lập trình thực tế.

Chúc các bạn học tốt và thực hành hiệu quả!

Comments

There are no comments at the moment.