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

Chào mừng các bạn quay trở lại với loạt bài viết về C++ của FullhouseDev!

Sau khi đã làm quen với các khái niệm lý thuyết về IteratorPair trong những bài học trước, đây là lúc chúng ta xắn tay áo lên và đi sâu vào thực hành! Việc luyện tập thông qua các bài tập cụ thể là cách tốt nhất để củng cố kiến thức và hiểu rõ hơn về cách áp dụng chúng trong lập trình C++.

Bài viết này sẽ tập trung hoàn toàn vào các bài tập thực hành với Iterator, Pair, và cả khi kết hợp cả hai. Hãy cùng bắt đầu nào!

Ôn lại nhanh: Iterator là gì?

Trước khi nhảy vào bài tập, hãy cùng ôn lại một chút về Iterator.

  • Iterator (Bộ lặp) là một khái niệm cực kỳ quan trọng trong C++, đặc biệt là khi làm việc với các Standard Template Library (STL) containers như vector, list, map, set, v.v.
  • Nó hoạt động giống như một con trỏ thông minh, cho phép chúng ta điều hướng qua các phần tử trong một container mà không cần quan tâm đến cấu trúc lưu trữ bên trong của container đó.
  • Mỗi container trong STL đều cung cấp các phương thức để lấy iterator, phổ biến nhất là begin() (trả về iterator trỏ tới phần tử đầu tiên) và end() (trả về iterator sau phần tử cuối cùng).

Ôn lại nhanh: Pair là gì?

Và không thể quên pair!

  • pair là một cấu trúc đơn giản cho phép gom nhóm hai giá trị (có thể khác kiểu dữ liệu) thành một đơn vị duy nhất.
  • Nó rất hữu ích khi bạn cần trả về hoặc lưu trữ một cặp thông tin liên quan với nhau.
  • Các thành phần của pair có thể được truy cập thông qua các thành viên công cộng .first.second.

Giờ thì, lý thuyết đã đủ rồi. Bắt tay vào code ngay thôi!

Bài tập thực hành với Iterator

Các bài tập sau đây sẽ giúp bạn làm quen với việc sử dụng iterator để duyệt và thao tác với các container.

Bài tập 1: Duyệt qua Vector bằng Iterator

Yêu cầu: Duyệt qua tất cả các phần tử của một vector<int> và in chúng ra màn hình sử dụng iterator.

Code:

#include <iostream>
#include <vector>

int main() {
    using namespace std;
    vector<int> vec = {10, 20, 30, 40, 50};

    cout << "Phan tu trong vector: ";

    for (auto i = vec.begin(); i != vec.end(); ++i) {
        cout << *i << " ";
    }
    cout << endl;

    return 0;
}

Output:

Phan tu trong vector: 10 20 30 40 50

Giải thích:

  • Chúng ta khai báo một iterator it và khởi tạo nó với numbers.begin(), trỏ đến phần tử đầu tiên (10).
  • Vòng lặp tiếp tục miễn là it chưa trỏ tới vị trí numbers.end() (vị trí sau phần tử cuối cùng).
  • ++it di chuyển iterator đến phần tử tiếp theo.
  • *it giải tham chiếu iterator, cho phép chúng ta truy cập vào giá trị của phần tử mà iterator đang trỏ tới.
Bài tập 2: Tìm kiếm phần tử trong Vector bằng Iterator

Yêu cầu: Tìm một giá trị cụ thể (ví dụ: 30) trong vector<int> sử dụng iterator và in ra thông báo nếu tìm thấy hoặc không tìm thấy.

Code:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    using namespace std;
    vector<int> vec = {10, 20, 30, 40, 50};
    int gt = 30;

    auto i = find(vec.begin(), vec.end(), gt);

    if (i != vec.end()) {
        cout << "Tim thay " << gt << " trong vector." << endl;
        cout << "Tai vi tri (index): " << distance(vec.begin(), i) << endl;
    } else {
        cout << gt << " khong tim thay trong vector." << endl;
    }

    return 0;
}

Output:

Tim thay 30 trong vector.
Tai vi tri (index): 2

Giải thích:

  • Hàm find (trong <algorithm>) nhận vào hai iterator defining phạm vi tìm kiếm (begin()end()) và giá trị cần tìm.
  • Nó trả về một iterator trỏ đến vị trí của phần tử đầu tiên tìm thấy.
  • Nếu không tìm thấy, nó trả về iterator numbers.end().
  • Chúng ta so sánh iterator trả về với numbers.end() để xác định xem phần tử có tồn tại hay không.
  • distance có thể tính khoảng cách giữa hai iterator, hữu ích để xác định chỉ mục (index) trong các container có chỉ mục như vector.
Bài tập 3: Duyệt ngược Vector bằng Reverse Iterator

Yêu cầu: Duyệt qua vector<int> theo chiều ngược lại và in các phần tử sử dụng reverse iterator.

Code:

#include <iostream>
#include <vector>

int main() {
    using namespace std;
    vector<int> vec = {10, 20, 30, 40, 50};

    cout << "Phan tu trong vector (duyet nguoc): ";

    for (auto i = vec.rbegin(); i != vec.rend(); ++i) {
        cout << *i << " ";
    }
    cout << endl;

    return 0;
}

Output:

Phan tu trong vector (duyet nguoc): 50 40 30 20 10

Giải thích:

  • Các container STL cung cấp reverse iterator thông qua rbegin()rend().
  • rbegin() trả về một reverse iterator trỏ đến phần tử cuối cùng.
  • rend() trả về một reverse iterator trỏ đến vị trí trước phần tử đầu tiên (tương tự như end() đối với forward iterator).
  • Điều đặc biệt là toán tử ++ khi dùng với reverse iterator lại khiến nó di chuyển ngược về phía đầu vector (từ phần tử cuối cùng về phần tử đầu tiên).

Bài tập thực hành với Pair

Các bài tập sau sẽ giúp bạn làm quen với việc tạo, truy cập và sử dụng pair.

Bài tập 4: Tạo và truy cập Pair cơ bản

Yêu cầu: Tạo một pair chứa một int (số thứ tự) và một string (tên), sau đó in ra các giá trị này.

Code:

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

int main() {
    using namespace std;
    pair<int, string> hs1(101, "Alice");
    auto hs2 = make_pair(102, "Bob");

    cout << "Thong tin hoc sinh 1:" << endl;
    cout << "  ID: " << hs1.first << endl;
    cout << "  Ten: " << hs1.second << endl;

    cout << "\nThong tin hoc sinh 2:" << endl;
    cout << "  ID: " << hs2.first << endl;
    cout << "  Ten: " << hs2.second << endl;

    return 0;
}

Output:

Thong tin hoc sinh 1:
  ID: 101
  Ten: Alice

Thong tin hoc sinh 2:
  ID: 102
  Ten: Bob

Giải thích:

  • Chúng ta có thể tạo pair bằng cách gọi constructor trực tiếp (như với student1) hoặc sử dụng hàm make_pair (thường tiện lợi hơn vì trình biên dịch có thể suy luận kiểu dữ liệu).
  • student1.firststudent1.second là cách để truy cập vào hai giá trị được lưu trữ bên trong pair.
Bài tập 5: Sử dụng Pair trong Vector

Yêu cầu: Tạo một vector chứa các pair<int, string>, sau đó duyệt qua vector này và in ra từng cặp giá trị.

Code:

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

int main() {
    using namespace std;
    vector<pair<int, string>> ds_sp = {
        {10, "Laptop"},
        {20, "Mouse"},
        {30, "Keyboard"}
    };

    cout << "Danh sach san pham:" << endl;

    for (const auto& sp : ds_sp) {
        cout << "  ID: " << sp.first << ", Ten: " << sp.second << endl;
    }

    return 0;
}

Output:

Danh sach san pham:
  ID: 10, Ten: Laptop
  ID: 20, Ten: Mouse
  ID: 30, Ten: Keyboard

Giải thích:

  • Chúng ta khai báo một vector mà mỗi phần tử của nó là một pair<int, string>.
  • Trong ví dụ này, chúng ta sử dụng range-based for loop (for (const auto& ...)), đây là một cách duyệt container hiện đại và thường gọn gàng hơn so với iterator thông thường khi bạn chỉ cần truy cập (không sửa đổi) các phần tử.
  • Bên trong vòng lặp, product_pair là một tham chiếu đến một pair trong vector, và chúng ta truy cập các giá trị bên trong pair bằng .first.second.

Kết hợp Iterator và Pair

Đây là lúc mọi thứ trở nên thú vị hơn! Pair và Iterator thường làm việc rất chặt chẽ với nhau, đặc biệt là trong các container như map.

Bài tập 6: Duyệt Map bằng Iterator (Map lưu trữ Pair)

Yêu cầu: Duyệt qua một map<string, int> (lưu trữ tên và tuổi) sử dụng iterator và in ra từng cặp key-value. (Lưu ý: Các phần tử trong map được lưu trữ dưới dạng pair<const Key, Value>).

Code:

#include <iostream>
#include <map>
#include <string>

int main() {
    using namespace std;
    map<string, int> ds_tuoi = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

    cout << "Danh sach ten va tuoi:" << endl;

    for (auto i = ds_tuoi.begin(); i != ds_tuoi.end(); ++i) {
        cout << "  Ten: " << i->first << ", Tuoi: " << i->second << endl;
    }

    return 0;
}

Output:

Danh sach ten va tuoi:
  Ten: Alice, Tuoi: 30
  Ten: Bob, Tuoi: 25
  Ten: Charlie, Tuoi: 35

Giải thích:

  • map là một container lưu trữ các cặp key-value đã được sắp xếp. Bên trong, mỗi phần tử được lưu trữ dưới dạng một pair, với key là phần tử .first (và nó là const), và value là phần tử .second.
  • Khi sử dụng iterator với map, iterator sẽ trỏ đến từng pair này.
  • Chúng ta dùng toán tử mũi tên -> (hoặc giải tham chiếu * rồi dùng dấu chấm .) để truy cập .first (key) và .second (value) của pair mà iterator đang trỏ tới.
Bài tập 7: Tìm và Sửa giá trị trong Vector of Pairs bằng Iterator

Yêu cầu: Tìm một pair trong vector<pair<int, string>> dựa trên giá trị .first (ID) sử dụng iterator, và sau đó sửa đổi giá trị .second (Tên) của pair đó.

Code:

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

int main() {
    using namespace std;
    vector<pair<int, string>> ds_sp = {
        {10, "Laptop"},
        {20, "Mouse"},
        {30, "Keyboard"}
    };

    int ma_tim = 20;
    string ten_moi = "Wireless Mouse";

    cout << "Vector ban dau:" << endl;
    for (const auto& sp : ds_sp) {
        cout << "  (" << sp.first << ", " << sp.second << ")" << endl;
    }

    auto i = find_if(ds_sp.begin(), ds_sp.end(),
        [ma_tim](const pair<int, string>& p){
            return p.first == ma_tim;
        });

    if (i != ds_sp.end()) {
        cout << "\nTim thay san pham co ID " << ma_tim << ". Dang cap nhat..." << endl;
        i->second = ten_moi;
        cout << "Da cap nhat ten moi: " << i->second << endl;
    } else {
        cout << "\nKhong tim thay san pham co ID " << ma_tim << "." << endl;
    }

    cout << "\nVector sau khi cap nhat:" << endl;
    for (const auto& sp : ds_sp) {
        cout << "  (" << sp.first << ", " << sp.second << ")" << endl;
    }

    return 0;
}

Output:

Vector ban dau:
  (10, Laptop)
  (20, Mouse)
  (30, Keyboard)

Tim thay san pham co ID 20. Dang cap nhat...
Da cap nhat ten moi: Wireless Mouse

Vector sau khi cap nhat:
  (10, Laptop)
  (20, Wireless Mouse)
  (30, Keyboard)

Giải thích:

  • Chúng ta sử dụng find_if để tìm kiếm. Hàm này linh hoạt hơn find vì nó cho phép chúng ta cung cấp một hàm điều kiện tùy chỉnh.
  • Hàm điều kiện ở đây là một lambda function [id_to_find](const pair<int, string>& p){ return p.first == id_to_find; }. Lambda này nhận một pair (tham chiếu hằng để hiệu quả) và trả về true nếu phần .first của pair đó khớp với id_to_find.
  • find_if sẽ trả về iterator trỏ đến pair đầu tiên thỏa mãn điều kiện, hoặc product_list.end() nếu không tìm thấy.
  • Nếu tìm thấy (iterator khác end()), chúng ta sử dụng it->second = new_name; để trực tiếp sửa đổi phần tử .second của pair thông qua iterator.

Các bài tập này chỉ là điểm khởi đầu. Hãy thử tự mình nghĩ ra thêm các kịch bản khác:

  • Sử dụng iterator để chèn hoặc xóa phần tử (cẩn thận với invalidation!).
  • Sử dụng iterator với các container khác như list hoặc set.
  • Sắp xếp một vector chứa các pair dựa trên .first hoặc .second.
  • Sử dụng pair để trả về nhiều giá trị từ một hàm.

Comments

There are no comments at the moment.