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() {
    vector<int> numbers = {10, 20, 30, 40, 50};

    cout << "Phan tu trong vector: ";

    // Khai bao iterator, bat dau tu phan tu dau tien (numbers.begin())
    for (auto it = numbers.begin(); // it bat dau tu begin()
         it != numbers.end();     // vong lap dung lai khi it tro toi end()
         ++it)                    // di chuyen iterator sang phan tu ke tiep
    {
        // Truy cap gia tri ma iterator dang tro toi dung toan tu *
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}

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> // Can find

int main() {
    vector<int> numbers = {10, 20, 30, 40, 50};
    int value_to_find = 30;

    // find tra ve iterator toi phan tu dau tien tim thay,
    // hoac numbers.end() neu khong tim thay
    auto it = find(numbers.begin(), numbers.end(), value_to_find);

    // Kiem tra xem iterator co phai la numbers.end() khong
    if (it != numbers.end()) {
        cout << "Tim thay " << value_to_find << " trong vector." << endl;
        // Neu muon biet vi tri (index), co the dung distance
        cout << "Tai vi tri (index): " << distance(numbers.begin(), it) << endl;
    } else {
        cout << value_to_find << " khong tim thay trong vector." << endl;
    }

    return 0;
}

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() {
    vector<int> numbers = {10, 20, 30, 40, 50};

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

    // Reverse iterator bat dau tu rbegin() va ket thuc tai rend()
    for (auto it = numbers.rbegin(); // rbegin() tro toi phan tu cuoi cung
         it != numbers.rend();     // vong lap dung lai khi it tro toi rend()
         ++it)                    // ++it di chuyen nguoc ve phia dau vector
    {
        // Truy cap gia tri
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}

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> // Can pair
#include <string>

int main() {
    // Tao mot pair su dung constructor
    pair<int, string> student1(101, "Alice");

    // Tao mot pair su dung make_pair (thuong dung hon vi ngan gon)
    auto student2 = make_pair(102, "Bob");

    cout << "Thong tin hoc sinh 1:" << endl;
    cout << "  ID: " << student1.first << endl; // Truy cap phan tu thu nhat
    cout << "  Ten: " << student1.second << endl; // Truy cap phan tu thu hai

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

    return 0;
}

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> // Can pair
#include <string>

int main() {
    // Tao mot vector chua cac pair
    vector<pair<int, string>> product_list = {
        {10, "Laptop"},
        {20, "Mouse"},
        {30, "Keyboard"}
    };

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

    // Duyet qua vector su dung range-based for loop (don gian hon iterator trong truong hop nay)
    for (const auto& product_pair : product_list) {
        // Moi phan tu trong vector bay gio la mot pair
        cout << "  ID: " << product_pair.first << ", Ten: " << product_pair.second << endl;
    }

    return 0;
}

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>
#include <utility> // Can pair (mac du map da bao gom)

int main() {
    // map luu tru cac cap (key, value) duoi dang pair<const Key, Value>
    map<string, int> ages = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };

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

    // Duyet map bang iterator
    for (auto it = ages.begin(); // it tro toi phan tu dau tien
         it != ages.end();     // vong lap ket thuc
         ++it)                 // di chuyen den phan tu ke tiep
    {
        // Iterator cua map tro toi mot pair<const string, int>
        // it->first la key (ten)
        // it->second la value (tuoi)
        cout << "  Ten: " << it->first << ", Tuoi: " << it->second << endl;

        // Hoac dung toan tu * roi moi den .first/.second: (*it).first, (*it).second
        // cout << "  Ten: " << (*it).first << ", Tuoi: " << (*it).second << endl;
    }

    return 0;
}

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> // Can pair
#include <string>
#include <algorithm> // Can find_if

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

    int id_to_find = 20;
    string new_name = "Wireless Mouse";

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

    // Tim pair co first element la id_to_find su dung find_if
    // find_if nhan vao pham vi va mot ham dieu kien (lambda function o day)
    auto it = find_if(product_list.begin(), product_list.end(),
        [id_to_find](const pair<int, string>& p){
            return p.first == id_to_find; // Ham dieu kien: pair co first == id_to_find?
        });

    // Kiem tra xem pair co duoc tim thay khong
    if (it != product_list.end()) {
        cout << "\nTim thay san pham co ID " << id_to_find << ". Dang cap nhat..." << endl;
        // it dang tro toi pair can cap nhat. Sua doi phan tu .second
        it->second = new_name;
        cout << "Da cap nhat ten moi: " << it->second << endl;
    } else {
        cout << "\nKhong tim thay san pham co ID " << id_to_find << "." << endl;
    }

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

    return 0;
}

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.