Bài 20.2: Bài tập thực hành Map cơ bản trong C++

Bài 20.2: Bài tập thực hành Map cơ bản trong C++
1. Sử dụng toán tử []
Đây là cách phổ biến và tiện lợi nhất cho hầu hết các trường hợp. Nếu khóa đã tồn tại, toán tử []
sẽ trả về tham chiếu đến giá trị hiện có để bạn có thể thay đổi nó. Nếu khóa chưa tồn tại, nó sẽ tự động thêm một cặp khóa-giá trị mới vào map với khóa được cung cấp và giá trị được khởi tạo mặc định cho kiểu dữ liệu đó (ví dụ: 0 cho int
, chuỗi rỗng cho string
, false
cho bool
).
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
m["Alice"] = 31; // Cập nhật
cout << "Tuoi Alice: " << m["Alice"] << endl;
cout << "Kich thuoc map truoc: " << m.size() << endl;
// Truy cap khoa chua ton tai, map se them moi
int tuoi_david = m["David"];
cout << "Tuoi David (them moi ngam): " << tuoi_david << endl;
cout << "Kich thuoc map sau: " << m.size() << endl;
return 0;
}
Output:
Tuoi Alice: 31
Kich thuoc map truoc: 2
Tuoi David (them moi ngam): 0
Kich thuoc map sau: 3
2. Sử dụng hàm insert()
Hàm insert()
cung cấp nhiều tùy chọn để thêm phần tử. Cách phổ biến nhất là chèn một pair
hoặc sử dụng danh sách khởi tạo (initializer list). Hàm insert
trả về một pair
chứa một iterator trỏ đến phần tử (đã được chèn hoặc đã tồn tại) và một bool
cho biết việc chèn có thực sự xảy ra hay không (true nếu là mới, false nếu khóa đã tồn tại). Quan trọng: insert
sẽ không ghi đè lên giá trị nếu khóa đã tồn tại.
#include <map>
#include <string>
#include <iostream>
#include <utility> // De su dung pair
int main() {
map<string, string> sach_dt;
auto kq1 = sach_dt.insert({"Alice", "0912345678"});
if (kq1.second) cout << "Them Alice: OK." << endl;
else cout << "Alice da co." << endl;
auto kq2 = sach_dt.insert({"Bob", "0987654321"});
if (kq2.second) cout << "Them Bob: OK." << endl;
else cout << "Bob da co." << endl;
auto kq3 = sach_dt.insert({"Alice", "0900000000"});
if (!kq3.second) {
cout << "Alice da co. SDT hien tai: " << kq3.first->second << endl;
}
return 0;
}
Output:
Them Alice: OK.
Them Bob: OK.
Alice da co. SDT hien tai: 0912345678
3. Sử dụng hàm emplace()
emplace()
tương tự như insert
, nhưng nó hiệu quả hơn một chút đối với các kiểu dữ liệu phức tạp vì nó xây dựng đối tượng giá trị tại chỗ trong map thay vì sao chép hoặc di chuyển một đối tượng đã tồn tại.
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, double> gia_sp;
auto kq1 = gia_sp.emplace("Apple", 1.2);
if (kq1.second) cout << "Them Apple: OK." << endl;
auto kq2 = gia_sp.emplace("Banana", 0.75);
if (kq2.second) cout << "Them Banana: OK." << endl;
auto kq3 = gia_sp.emplace("Apple", 1.5);
if (!kq3.second) {
cout << "Apple da co. Gia hien tai: " << kq3.first->second << endl;
}
return 0;
}
Output:
Them Apple: OK.
Them Banana: OK.
Apple da co. Gia hien tai: 1.2
Truy Cập Phần Tử trong map
Có hai cách chính để truy cập giá trị dựa trên khóa:
1. Sử dụng toán tử []
Như đã đề cập ở phần thêm, toán tử []
trả về tham chiếu đến giá trị. Nếu khóa tồn tại, bạn sẽ nhận được tham chiếu đến giá trị đó. Nếu khóa không tồn tại, nó sẽ thêm khóa đó vào map với giá trị mặc định và trả về tham chiếu đến giá trị mặc định đó.
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
int tuoi_alice = m["Alice"];
cout << "Tuoi Alice: " << tuoi_alice << endl;
cout << "Kich thuoc map ban dau: " << m.size() << endl;
int tuoi_charlie = m["Charlie"]; // Them Charlie voi gia tri mac dinh
cout << "Tuoi Charlie (sau khi truy cap): " << tuoi_charlie << endl;
cout << "Kich thuoc map sau: " << m.size() << endl;
return 0;
}
Output:
Tuoi Alice: 30
Kich thuoc map ban dau: 2
Tuoi Charlie (sau khi truy cap): 0
Kich thuoc map sau: 3
2. Sử dụng hàm at()
Hàm at()
cũng trả về tham chiếu đến giá trị cho khóa được cung cấp. Tuy nhiên, điểm khác biệt quan trọng là nếu khóa không tồn tại, at()
sẽ ném ra một ngoại lệ kiểu out_of_range
. Điều này giúp bạn phát hiện lỗi khi cố gắng truy cập một khóa không hợp lệ.
#include <map>
#include <string>
#include <iostream>
#include <stdexcept> // De su dung out_of_range
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
try {
int tuoi_bob = m.at("Bob");
cout << "Tuoi Bob: " << tuoi_bob << endl;
} catch (const out_of_range& e) {
cerr << "Loi: " << e.what() << endl;
}
try {
int tuoi_charlie = m.at("Charlie"); // Charlie chua ton tai
cout << "Tuoi Charlie: " << tuoi_charlie << endl;
} catch (const out_of_range& e) {
cerr << "Loi: " << e.what() << endl;
}
cout << "Kich thuoc map: " << m.size() << endl;
return 0;
}
Output:
Tuoi Bob: 25
Loi: map::at
Kich thuoc map: 2
Duyệt (Iterate) qua map
Bạn có thể duyệt qua tất cả các cặp khóa-giá trị trong map bằng cách sử dụng vòng lặp dựa trên phạm vi (range-based for loop) hoặc iterator. Các phần tử sẽ được duyệt theo thứ tự tăng dần của khóa.
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Charlie"] = 35;
m["Alice"] = 30;
m["Bob"] = 25;
cout << "Duyet range-based for:" << endl;
for (const auto& p : m) {
cout << p.first << ": " << p.second << endl;
}
cout << "\nDuyet iterator:" << endl;
for (auto i = m.begin(); i != m.end(); ++i) {
cout << i->first << ": " << i->second << endl;
}
return 0;
}
Output:
Duyet range-based for:
Alice: 30
Bob: 25
Charlie: 35
Duyet iterator:
Alice: 30
Bob: 25
Charlie: 35
Kiểm Tra Sự Tồn Tại của Khóa
Trước khi truy cập một khóa bằng []
hoặc at()
, bạn thường cần kiểm tra xem khóa đó có tồn tại trong map hay không. Có hai cách chính:
1. Sử dụng hàm count()
Hàm count(key)
trả về số lượng phần tử có khóa bằng key
. Vì khóa trong map
là duy nhất, count()
sẽ trả về 1
nếu khóa tồn tại và 0
nếu không.
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
if (m.count("Alice")) {
cout << "Alice co trong map." << endl;
} else {
cout << "Alice khong co trong map." << endl;
}
if (m.count("Charlie")) {
cout << "Charlie co trong map." << endl;
} else {
cout << "Charlie khong co trong map." << endl;
}
return 0;
}
Output:
Alice co trong map.
Charlie khong co trong map.
2. Sử dụng hàm find()
Hàm find(key)
trả về một iterator trỏ đến phần tử có khóa bằng key
nếu tìm thấy. Nếu không tìm thấy, nó trả về iterator map.end()
. Đây là cách hiệu quả hơn nếu bạn có ý định truy cập hoặc xóa phần tử ngay sau khi tìm thấy nó, vì bạn đã có sẵn iterator.
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
auto i_a = m.find("Alice");
if (i_a != m.end()) {
cout << "Tim thay Alice! Tuoi: " << i_a->second << endl;
} else {
cout << "Alice khong tim thay." << endl;
}
auto i_c = m.find("Charlie");
if (i_c != m.end()) {
cout << "Tim thay Charlie! Tuoi: " << i_c->second << endl;
} else {
cout << "Charlie khong tim thay." << endl;
}
return 0;
}
Output:
Tim thay Alice! Tuoi: 30
Charlie khong tim thay.
Xóa Phần Tử khỏi map
Bạn có thể xóa phần tử khỏi map bằng khóa, iterator, hoặc một phạm vi iterator.
1. Xóa bằng khóa
Hàm erase(key)
xóa phần tử có khóa bằng key
. Nó trả về số lượng phần tử đã xóa (sẽ là 0 hoặc 1).
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
m["Charlie"] = 35;
cout << "Map ban dau: " << m.size() << " phan tu." << endl;
size_t sl_xoa = m.erase("Bob");
if (sl_xoa > 0) {
cout << "Xoa Bob: OK. Con " << m.size() << " phan tu." << endl;
}
size_t sl_xoa_david = m.erase("David");
if (sl_xoa_david == 0) {
cout << "David khong co trong map, khong xoa." << endl;
}
return 0;
}
Output:
Map ban dau: 3 phan tu.
Xoa Bob: OK. Con 2 phan tu.
David khong co trong map, khong xoa.
2. Xóa bằng Iterator
Hàm erase(iterator)
xóa phần tử mà iterator trỏ tới. Nó trả về một iterator trỏ đến phần tử tiếp theo sau phần tử bị xóa (điều này rất hữu ích khi xóa trong vòng lặp).
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
m["Charlie"] = 35;
auto i_b = m.find("Bob");
if (i_b != m.end()) {
auto i_tiep = m.erase(i_b);
cout << "Xoa Bob bang iterator: OK." << endl;
if (i_tiep != m.end()) {
cout << "Phan tu tiep theo: " << i_tiep->first << endl;
}
}
cout << "Map hien tai: " << m.size() << " phan tu." << endl;
return 0;
}
Output:
Xoa Bob bang iterator: OK.
Phan tu tiep theo: Charlie
Map hien tai: 2 phan tu.
Các Thao Tác Cơ Bản Khác
Map cũng hỗ trợ các thao tác cơ bản khác:
size()
: Trả về số lượng phần tử trong map.empty()
: Trả vềtrue
nếu map rỗng,false
nếu ngược lại.clear()
: Xóa tất cả các phần tử khỏi map.
#include <map>
#include <string>
#include <iostream>
int main() {
map<string, int> m;
m["Alice"] = 30;
m["Bob"] = 25;
cout << "Kich thuoc ban dau: " << m.size() << endl;
cout << "Map rong? " << (m.empty() ? "Co" : "Khong") << endl;
m.clear();
cout << "Kich thuoc sau clear: " << m.size() << endl;
cout << "Map rong sau clear? " << (m.empty() ? "Co" : "Khong") << endl;
return 0;
}
Output:
Kich thuoc ban dau: 2
Map rong? Khong
Kich thuoc sau clear: 0
Map rong sau clear? Co
Khi nào thì dùng map
?
Bạn nên cân nhắc sử dụng map
khi:
- Bạn cần lưu trữ dữ liệu dưới dạng cặp khóa-giá trị.
- Các khóa cần phải duy nhất.
- Bạn cần các phần tử được sắp xếp tự động theo khóa.
- Hiệu suất truy xuất, thêm, xóa logarithmic (O(log n)) là chấp nhận được cho ứng dụng của bạn.
Nếu bạn không cần các phần tử được sắp xếp và muốn hiệu suất trung bình O(1) cho các thao tác cơ bản, unordered_map
là lựa chọn tốt hơn (sẽ được tìm hiểu trong các bài sau).
Một Ví Dụ Thực Tế Nhỏ: Đếm Tần Suất Từ
Đây là một ví dụ kinh điển thể hiện sức mạnh của map
: đếm số lần xuất hiện của mỗi từ trong một chuỗi văn bản.
#include <iostream>
#include <map>
#include <string>
#include <sstream> // De tach chuoi thanh tu
#include <cctype> // De xu ly ky tu (chuyen thanh chu thuong)
#include <algorithm> // De su dung transform
int main() {
string v = "This is a sample text. This text is a sample.";
map<string, int> dem_tu;
stringstream ss(v);
string t;
while (ss >> t) {
transform(t.begin(), t.end(), t.begin(), ::tolower);
while (!t.empty() && ispunct(t.back())) {
t.pop_back();
}
if (!t.empty()) {
dem_tu[t]++;
}
}
cout << "Tan suat cac tu:" << endl;
for (const auto& p : dem_tu) {
cout << p.first << ": " << p.second << endl;
}
return 0;
}
Output: ``` Tan suat cac tu: a: 2 is: 2 sample: 2 text: 2 this: 2
Comments