C20: Lambda & Iterator Pattern
Tác giả: Hà Trí Kiên
Chủ đề: Lambda expressions, iterator patterns, erase-remove idiom, range-based for
1. Tổng quan
Bài này tổng hợp các pattern nâng cao với lambda và iterator — thường gặp trong thi đấu và lập trình hiện đại.
flowchart TD
A["Lambda & Iterator"] --> B["Lambda expressions"]
A --> C["Iterator patterns"]
A --> D["Erase-remove idiom"]
A --> E["Range-based for patterns"]
B --> B1["Cú pháp cơ bản"]
B --> B2["Capture list"]
B --> B3["Generic lambda"]
B --> B4["Lambda với STL"]
C --> C1["Reverse iterator"]
C --> C2["Iterator invalidation"]
C --> C3["Insert/Erase patterns"]
2. Lambda Expressions
2.1. Cú pháp cơ bản
| // Cú pháp: [capture](parameters) -> return_type { body }
// Lambda không tham số, không trả về
auto hello = []() { cout << "Hello!" << endl; };
hello(); // Hello!
// Lambda có tham số
auto add = [](int a, int b) { return a + b; };
cout << add(3, 5) << endl; // 8
// Lambda với return type rõ ràng
auto divide = [](double a, double b) -> double {
if (b == 0) return 0;
return a / b;
};
|
2.2. Capture list — Biến bên ngoài
| #include <bits/stdc++.h>
using namespace std;
int main() {
int x = 10;
int y = 20;
// Capture by value (sao chép)
auto f1 = [x, y]() { return x + y; };
cout << f1() << endl; // 30
// Capture by reference (tham chiếu)
auto f2 = [&x, &y]() { x++; y++; };
f2();
cout << x << " " << y << endl; // 11 21
// Capture tất cả by value
auto f3 = [=]() { return x + y; };
// Capture tất cả by reference
auto f4 = [&]() { x++; y++; };
// Mixed: x by value, y by reference
auto f5 = [x, &y]() { y += x; };
return 0;
}
|
2.3. Generic Lambda (C++14)
| #include <bits/stdc++.h>
using namespace std;
int main() {
// Lambda với auto parameter (generic)
auto print = [](const auto &v) {
for (const auto &x : v) cout << x << " ";
cout << endl;
};
vector<int> a = {1, 2, 3};
vector<string> b = {"hello", "world"};
print(a); // 1 2 3
print(b); // hello world
// Generic lambda với nhiều tham số
auto max_val = [](const auto &a, const auto &b) {
return a > b ? a : b;
};
cout << max_val(3, 5) << endl; // 5
cout << max_val(3.14, 2.71) << endl; // 3.14
return 0;
}
|
2.4. Lambda với STL
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {5, 2, 8, 1, 9, 3};
// sort với lambda
sort(a.begin(), a.end(), [](int x, int y) {
return x > y; // Giảm dần
});
// find_if với lambda
auto it = find_if(a.begin(), a.end(), [](int x) {
return x % 2 == 0; // Tìm số chẵn đầu tiên
});
// count_if với lambda
int cnt = count_if(a.begin(), a.end(), [](int x) {
return x > 5;
});
// for_each với lambda
for_each(a.begin(), a.end(), [](int &x) {
x *= 2;
});
// transform với lambda
vector<int> b(a.size());
transform(a.begin(), a.end(), b.begin(), [](int x) {
return x * x;
});
return 0;
}
|
2.5. Lambda đệ quy
| #include <bits/stdc++.h>
using namespace std;
int main() {
// Lambda đệ quy với std::function
function<int(int)> fib = [&](int n) -> int {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
};
for (int i = 0; i < 10; i++) {
cout << fib(i) << " ";
}
cout << endl;
// Output: 0 1 1 2 3 5 8 13 21 34
// Lambda đệ quy với Y-combinator (C++23 hoặc custom)
// Không khuyến nghị cho thi đấu, dùng function<> cho đơn giản
return 0;
}
|
3. Iterator Patterns
3.1. Reverse Iterator
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 3, 4, 5};
// Duyệt ngược
for (auto it = a.rbegin(); it != a.rend(); ++it) {
cout << *it << " ";
}
cout << endl;
// Output: 5 4 3 2 1
// Tìm phần tử cuối cùng thỏa mãn điều kiện
auto it = find_if(a.rbegin(), a.rend(), [](int x) {
return x % 2 == 0;
});
if (it != a.rend()) {
cout << "So chan cuoi cung: " << *it << endl; // 4
}
return 0;
}
|
3.2. Iterator Invalidation — Lỗi thường gặp
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// SAI: Xóa phần tử trong vòng for duyệt iterator
// for (auto it = a.begin(); it != a.end(); ++it) {
// if (*it % 2 == 0) a.erase(it); // LỖI: iterator bị invalidate
// }
// ĐÚNG: erase trả về iterator mới
for (auto it = a.begin(); it != a.end(); ) {
if (*it % 2 == 0) {
it = a.erase(it); // erase trả về iterator sau phần tử bị xóa
} else {
++it;
}
}
for (int x : a) cout << x << " ";
cout << endl;
// Output: 1 3 5 7 9
return 0;
}
|
3.3. Insert với Iterator
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 5, 6};
// Chèn 3, 4 trước vị trí 5
auto it = find(a.begin(), a.end(), 5);
a.insert(it, {3, 4}); // Chèn danh sách
for (int x : a) cout << x << " ";
cout << endl;
// Output: 1 2 3 4 5 6
// Chèn từ vector khác
vector<int> b = {10, 20};
a.insert(a.end(), b.begin(), b.end());
for (int x : a) cout << x << " ";
cout << endl;
// Output: 1 2 3 4 5 6 10 20
return 0;
}
|
3.4. advance / next / prev / distance
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {10, 20, 30, 40, 50};
// next: iterator cách k vị trí (không thay đổi iterator gốc)
auto it = next(a.begin(), 2);
cout << *it << endl; // 30
// prev: iterator lùi k vị trí
auto it2 = prev(a.end(), 1);
cout << *it2 << endl; // 50
// advance: di chuyển iterator (thay đổi iterator gốc)
auto it3 = a.begin();
advance(it3, 3);
cout << *it3 << endl; // 40
// distance: khoảng cách giữa 2 iterator
int d = distance(a.begin(), a.end());
cout << d << endl; // 5
return 0;
}
|
4. Erase-Remove Idiom
4.1. Xóa phần tử thỏa mãn điều kiện
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Xóa tất cả số chẵn
// Bước 1: remove_if di chuyển phần tử không bị xóa lên đầu
// Bước 2: erase xóa phần tử thừa ở cuối
a.erase(remove_if(a.begin(), a.end(), [](int x) {
return x % 2 == 0;
}), a.end());
for (int x : a) cout << x << " ";
cout << endl;
// Output: 1 3 5 7 9
return 0;
}
|
4.2. Xóa phần tử trùng lặp
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 2, 3, 3, 3, 4, 4, 4, 4};
// unique chỉ hoạt động trên mảng đã sắp xếp
a.erase(unique(a.begin(), a.end()), a.end());
for (int x : a) cout << x << " ";
cout << endl;
// Output: 1 2 3 4
return 0;
}
|
4.3. Xóa phần tử trong map/set
| #include <bits/stdc++.h>
using namespace std;
int main() {
map<string, int> mp = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}};
// Xóa phần tử có giá trị <= 2
for (auto it = mp.begin(); it != mp.end(); ) {
if (it->second <= 2) {
it = mp.erase(it);
} else {
++it;
}
}
for (auto [k, v] : mp) {
cout << k << ": " << v << endl;
}
// Output:
// c: 3
// d: 4
return 0;
}
|
5. Range-based For Patterns
5.1. Duyệt với auto& vs const auto&
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 3, 4, 5};
// const auto&: chỉ đọc, không sửa
for (const auto &x : a) {
cout << x << " ";
}
cout << endl;
// auto&: sửa được
for (auto &x : a) {
x *= 2;
}
// auto: sao chép (không nên dùng với object lớn)
for (auto x : a) {
cout << x << " ";
}
cout << endl;
return 0;
}
|
5.2. Duyệt map
| #include <bits/stdc++.h>
using namespace std;
int main() {
map<string, int> mp = {{"apple", 3}, {"banana", 1}, {"cherry", 2}};
// C++17: structured bindings
for (const auto &[key, value] : mp) {
cout << key << " -> " << value << endl;
}
// C++11/14
for (const auto &p : mp) {
cout << p.first << " -> " << p.second << endl;
}
return 0;
}
|
5.3. Duyệt với index
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {10, 20, 30, 40, 50};
// Cách 1: traditional for (khuyến nghị cho thi đấu)
for (int i = 0; i < (int)a.size(); i++) {
cout << i << ": " << a[i] << endl;
}
// Cách 2: enumerate pattern
int idx = 0;
for (const auto &x : a) {
cout << idx++ << ": " << x << endl;
}
return 0;
}
|
5.4. Duyệt nhiều container cùng lúc
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> keys = {1, 2, 3, 4, 5};
vector<string> values = {"one", "two", "three", "four", "five"};
// Duyệt song song (giả sử cùng kích thước)
for (int i = 0; i < (int)keys.size(); i++) {
cout << keys[i] << " -> " << values[i] << endl;
}
// Hoặc dùng zip pattern (C++ không có zip, tự implement)
auto zip = [](const auto &a, const auto &b) {
vector<pair<
typename decltype(a)::value_type,
typename decltype(b)::value_type
>> result;
size_t n = min(a.size(), b.size());
for (size_t i = 0; i < n; i++) {
result.emplace_back(a[i], b[i]);
}
return result;
};
for (const auto &[k, v] : zip(keys, values)) {
cout << k << " -> " << v << endl;
}
return 0;
}
|
6. Pattern thường gặp trong thi đấu
6.1. Custom comparator với lambda
| #include <bits/stdc++.h>
using namespace std;
int main() {
// Sắp xếp pair theo second giảm dần, nếu bằng thì theo first tăng dần
vector<pair<int,int>> a = {{1, 3}, {2, 1}, {3, 3}, {4, 2}};
sort(a.begin(), a.end(), [](const auto &x, const auto &y) {
if (x.second != y.second) return x.second > y.second;
return x.first < y.first;
});
for (auto [x, y] : a) {
cout << "(" << x << "," << y << ") ";
}
cout << endl;
// Output: (1,3) (3,3) (4,2) (2,1)
return 0;
}
|
6.2. Priority queue với lambda
| #include <bits/stdc++.h>
using namespace std;
int main() {
// Min-heap với lambda
auto cmp = [](const pair<int,int> &a, const pair<int,int> &b) {
return a.first > b.first; // Min-heap
};
priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(cmp)> pq(cmp);
pq.push({3, 100});
pq.push({1, 200});
pq.push({2, 300});
while (!pq.empty()) {
auto [val, id] = pq.top(); pq.pop();
cout << val << " ";
}
cout << endl;
// Output: 1 2 3
return 0;
}
|
6.3. Accumulate với lambda
| #include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> a = {1, 2, 3, 4, 5};
// Tổng bình phương
int sumSq = accumulate(a.begin(), a.end(), 0,
[](int acc, int x) { return acc + x * x; });
cout << sumSq << endl; // 55
// Tìm max bằng accumulate
int maxVal = accumulate(a.begin(), a.end(), INT_MIN,
[](int acc, int x) { return max(acc, x); });
cout << maxVal << endl; // 5
return 0;
}
|
7. Bài tập thực hành
Bài 1: Sắp xếp theo tần suất
Cho mảng \(n\) số nguyên. Sắp xếp các phần tử theo tần suất xuất hiện giảm dần. Nếu bằng tần suất, sắp xếp theo giá trị tăng dần.
Lời giải
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
map<int, int> freq;
for (int x : a) freq[x]++;
sort(a.begin(), a.end(), [&](int x, int y) {
if (freq[x] != freq[y]) return freq[x] > freq[y];
return x < y;
});
// Xóa trùng (giữ thứ tự)
a.erase(unique(a.begin(), a.end()), a.end());
for (int x : a) {
for (int i = 0; i < freq[x]; i++) cout << x << " ";
}
cout << endl;
return 0;
}
|
Bài 2: Top-K frequent elements
Cho mảng \(n\) số nguyên và số \(k\). Tìm \(k\) phần tử xuất hiện nhiều nhất.
Lời giải
| #include <bits/stdc++.h>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
map<int, int> freq;
for (int x : a) freq[x]++;
vector<pair<int,int>> items(freq.begin(), freq.end());
sort(items.begin(), items.end(), [](const auto &x, const auto &y) {
return x.second > y.second;
});
for (int i = 0; i < k && i < (int)items.size(); i++) {
cout << items[i].first << " ";
}
cout << endl;
return 0;
}
|
Bài viết liên quan
Quay lại: Chương 2: C++ cho Thi Đấu