Skip to content

STL Algorithms & Lambdas: Sức Mạnh Kết Hợp Module 3

Ngừng viết raw loop. STL algorithms + lambdas là combo mạnh nhất trong C++ hiện đại — code ngắn hơn, rõ ý hơn, và thường nhanh hơn cả vòng lặp bạn tự viết. Bài này biến bạn thành người viết code C++ thực sự biểu cảm.

🎯 Mục tiêu

Sau bài này, bạn sẽ:

  • Nắm vững các STL algorithms quan trọng nhất: sort, find, count, transform, for_each, accumulate
  • Hiểu cú pháp lambda và capture semantics — khi nào dùng [=], [&], [this]
  • Viết code C++ ngắn gọn, biểu cảm, thay thế hoàn toàn raw for loop
  • Biết idiom Erase-Remove và preview C++20 Ranges
  • Áp dụng algorithms + lambdas vào bài toán thực tế (Order Analytics)

🧠 1. Triết Lý: "Đừng Tự Viết Loop"

Trước khi đi vào cú pháp, hãy hiểu tại sao STL algorithms tồn tại.

Raw Loop vs. Algorithm — Không chỉ là style

cpp
// ❌ Raw loop — ý định không rõ ràng
for (int i = 0; i < v.size(); ++i) {
    if (v[i] == target) {
        // tìm thấy...
        break;
    }
}

// ✅ Algorithm — ý định crystal clear
auto it = std::find(v.begin(), v.end(), target);

📌 Nguyên tắc vàng

Khi bạn viết std::find, bất kỳ C++ developer nào đọc code đều biết ngay: "Đang tìm một phần tử." Raw for loop không nói gì về ý định — người đọc phải đọc toàn bộ loop body mới hiểu bạn đang làm gì.

Tại sao STL Algorithms thắng?

Tiêu chíRaw LoopSTL Algorithm
Ý địnhPhải đọc body mới hiểuTên algorithm = ý định
Tốc độThường chậm hơnTối ưu bởi compiler + lib authors
BugDễ sai index, off-by-oneBattle-tested, hàng triệu người dùng
Bảo trìKhó refactorDễ thay đổi logic (đổi lambda)
ParallelismPhải viết lại từ đầuThêm execution policy (C++17)

🎓 Bạn có biết?

std::sort sử dụng introsort — kết hợp quicksort + heapsort + insertion sort. Nó tự động chọn chiến lược tốt nhất tùy thuộc vào dữ liệu. Bạn gần như không bao giờ viết được sort nhanh hơn std::sort.


🔧 2. Lambda Expressions — Cú Pháp Cốt Lõi

Lambda là anonymous function — hàm không tên, định nghĩa ngay tại chỗ sử dụng.

Cú pháp tổng quát

cpp
[capture](parameters) -> return_type { body }

Trong đó:

  • [capture] — biến nào từ scope ngoài được dùng bên trong lambda
  • (parameters) — tham số giống function bình thường
  • -> return_type — kiểu trả về (thường bỏ qua, compiler tự suy)
  • { body } — thân hàm

Ví dụ cơ bản

cpp
// Lambda đơn giản — tính bình phương
auto square = [](int x) { return x * x; };
std::cout << square(5);  // 25

// Lambda với string_view — chào hỏi
auto greet = [](std::string_view name) {
    std::cout << "Xin chào, " << name << "!\n";
};
greet("Penalgo");  // Xin chào, Penalgo!

// Lambda không tham số
auto dice = []() { return rand() % 6 + 1; };

Lambda với auto parameter (C++14+)

cpp
// Generic lambda — hoạt động với mọi kiểu
auto print = [](const auto& value) {
    std::cout << value << "\n";
};

print(42);           // int
print(3.14);         // double
print("Penalgo"s);   // string

🔑 Mẹo nhớ

Lambda = hàm nhỏ dùng một lần, thường truyền vào algorithm. Nếu lambda dài hơn 5-6 dòng, hãy cân nhắc viết thành named function.


🎯 3. Capture Semantics — Trọng Tâm Của Lambda

Capture quyết định lambda truy cập được biến nào từ scope bên ngoài.

Bảng capture modes

Cú phápÝ nghĩaKhi nào dùng
[]Không capture gìLambda độc lập, chỉ dùng parameters
[x]Capture x by value (copy)Cần giá trị tại thời điểm tạo lambda
[&x]Capture x by referenceCần thay đổi x hoặc tránh copy đắt
[=]Capture tất cả by valueTiện nhưng nên tránh — không rõ ràng
[&]Capture tất cả by referenceNguy hiểm nếu lambda outlive scope
[=, &x]Tất cả by value, riêng x by referenceCần đặc biệt thay đổi x
[this]Capture con trỏ thisDùng trong member function

Ví dụ từng mode

cpp
int threshold = 100;
std::string label = "Expensive";

// [x] — capture by value
auto check_v = [threshold](int price) {
    return price > threshold;
    // threshold là BẢN SAO — thay đổi threshold bên ngoài không ảnh hưởng
};

// [&x] — capture by reference
auto check_r = [&threshold](int price) {
    threshold += 10;  // THAY ĐỔI biến gốc!
    return price > threshold;
};

// [=] — capture all by value
auto describe = [=](int price) {
    // Dùng được cả threshold và label (bản sao)
    if (price > threshold) {
        std::cout << label << ": " << price << "\n";
    }
};

// [&] — capture all by reference
auto adjust = [&](int delta) {
    threshold += delta;  // Thay đổi threshold gốc
    label = "Adjusted";  // Thay đổi label gốc
};

// [=, &x] — mix mode
auto mixed = [=, &threshold](int price) {
    threshold = price;  // Thay đổi threshold gốc
    // label là bản sao — không đổi label gốc
    std::cout << label << ": " << threshold << "\n";
};

Capture this trong class

cpp
class OrderProcessor {
    double tax_rate_ = 0.1;

    void process(std::vector<double>& prices) {
        // [this] — truy cập member variables
        std::transform(prices.begin(), prices.end(), prices.begin(),
            [this](double price) {
                return price * (1 + tax_rate_);
            });
    }
};

⚠️ Capture by Reference — Mối Nguy Hiểm Ẩn

cpp
// ❌ NGUY HIỂM — Dangling Reference!
std::function<int()> create_counter() {
    int count = 0;
    return [&count]() { return ++count; };
    // count bị hủy khi hàm return
    // lambda vẫn giữ reference → UNDEFINED BEHAVIOR
}

// ✅ AN TOÀN — Capture by value
std::function<int()> create_counter() {
    int count = 0;
    return [count]() mutable { return ++count; };
    // count là bản sao — sống cùng lambda
}

Quy tắc: Nếu lambda outlive scope tạo ra nó (return, lưu vào biến, truyền sang thread khác), PHẢI capture by value.


🗡️ 4. Core Algorithms Tour

Đây là những algorithm bạn sẽ dùng hàng ngày. Tất cả nằm trong <algorithm> (trừ accumulate<numeric>).

4.1 std::sort — Sắp xếp

cpp
#include <algorithm>
#include <vector>

std::vector<int> nums = {5, 2, 8, 1, 9, 3};

// Sắp xếp tăng dần (mặc định)
std::sort(nums.begin(), nums.end());
// nums = {1, 2, 3, 5, 8, 9}

// Sắp xếp giảm dần
std::sort(nums.begin(), nums.end(), std::greater<>{});
// nums = {9, 8, 5, 3, 2, 1}

Custom comparator với lambda — ví dụ thực tế:

cpp
struct Order {
    std::string customer;
    double amount;
    std::string date;  // "2024-01-15"
};

std::vector<Order> orders = { /* ... */ };

// Sắp xếp theo amount giảm dần
std::sort(orders.begin(), orders.end(),
    [](const Order& a, const Order& b) {
        return a.amount > b.amount;
    });

// Sắp xếp theo date, nếu trùng thì theo amount giảm
std::sort(orders.begin(), orders.end(),
    [](const Order& a, const Order& b) {
        if (a.date != b.date) return a.date < b.date;
        return a.amount > b.amount;
    });

🎓 std::stable_sort

Nếu cần giữ thứ tự tương đối của các phần tử bằng nhau, dùng std::stable_sort. Nó chậm hơn một chút (O(n log²n) worst case) nhưng ổn định — quan trọng khi sort theo nhiều tiêu chí.

4.2 std::find / std::find_if — Tìm kiếm

cpp
std::vector<int> nums = {10, 20, 30, 42, 50};

// Tìm giá trị cụ thể
auto it = std::find(nums.begin(), nums.end(), 42);
if (it != nums.end()) {
    std::cout << "Tìm thấy " << *it << " tại index "
              << std::distance(nums.begin(), it) << "\n";
}

// Tìm theo điều kiện — order đầu tiên > 1 triệu
auto expensive = std::find_if(orders.begin(), orders.end(),
    [](const Order& o) { return o.amount > 1'000'000; });

if (expensive != orders.end()) {
    std::cout << "Đơn hàng lớn: " << expensive->customer
              << " - " << expensive->amount << "\n";
}

// Tìm phần tử KHÔNG thỏa điều kiện
auto non_premium = std::find_if_not(orders.begin(), orders.end(),
    [](const Order& o) { return o.amount > 500'000; });

4.3 std::count / std::count_if — Đếm

cpp
std::vector<int> scores = {85, 92, 67, 45, 91, 78, 95, 55};

// Đếm số điểm A (>= 90)
int grade_a = std::count_if(scores.begin(), scores.end(),
    [](int score) { return score >= 90; });
std::cout << "Số học sinh đạt A: " << grade_a << "\n";  // 3

// Đếm chính xác giá trị
int perfect = std::count(scores.begin(), scores.end(), 100);

// Đếm đơn hàng vượt ngưỡng — capture threshold
double min_amount = 500'000;
int big_orders = std::count_if(orders.begin(), orders.end(),
    [min_amount](const Order& o) {
        return o.amount >= min_amount;
    });

4.4 std::transform — Biến đổi

transform tạo output mới từ input — giống map() trong các ngôn ngữ khác.

cpp
std::vector<double> prices = {100.0, 250.0, 80.0, 420.0};

// Tính giá sau thuế 10%
std::vector<double> with_tax;
std::transform(prices.begin(), prices.end(),
    std::back_inserter(with_tax),
    [](double p) { return p * 1.1; });
// with_tax = {110.0, 275.0, 88.0, 462.0}

// Transform in-place — viết hoa tất cả ký tự
std::string name = "penalgo";
std::transform(name.begin(), name.end(), name.begin(),
    [](char c) { return std::toupper(c); });
// name = "PENALGO"

// Transform 2 container — tính tổng từng cặp
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {10, 20, 30};
std::vector<int> sums;
std::transform(a.begin(), a.end(), b.begin(),
    std::back_inserter(sums),
    [](int x, int y) { return x + y; });
// sums = {11, 22, 33}

4.5 std::accumulate — Tổng hợp

Nằm trong <numeric>, không phải <algorithm>.

cpp
#include <numeric>

std::vector<double> prices = {100.0, 250.0, 80.0, 420.0};

// Tính tổng
double total = std::accumulate(prices.begin(), prices.end(), 0.0);
// total = 850.0

// ⚠️ Cẩn thận kiểu init value!
// int total = std::accumulate(prices.begin(), prices.end(), 0);
// → Kết quả SAI vì 0 là int, tất cả bị truncate!

// Custom operation — tính tích
double product = std::accumulate(prices.begin(), prices.end(), 1.0,
    [](double acc, double price) { return acc * price; });

// Tính tổng amount từ vector<Order>
double total_revenue = std::accumulate(orders.begin(), orders.end(), 0.0,
    [](double sum, const Order& o) { return sum + o.amount; });

⚠️ Bẫy Kiểu Init Value

cpp
std::vector<double> vals = {1.5, 2.5, 3.5};

// ❌ Init value = 0 (int) → kết quả bị truncate thành int
auto bad = std::accumulate(vals.begin(), vals.end(), 0);    // = 6 (int!)

// ✅ Init value = 0.0 (double) → kết quả đúng
auto good = std::accumulate(vals.begin(), vals.end(), 0.0); // = 7.5

Kiểu của init value quyết định kiểu của kết quả. Luôn dùng 0.0 cho double, 0L cho long.

4.6 std::for_each — Duyệt và thực thi

cpp
std::vector<std::string> names = {"An", "Bình", "Cường", "Dũng"};

// In từng phần tử
std::for_each(names.begin(), names.end(),
    [](const std::string& name) {
        std::cout << "Xin chào " << name << "!\n";
    });

// Thay đổi in-place (capture by reference không cần — parameter là reference)
std::vector<int> nums = {1, 2, 3, 4, 5};
std::for_each(nums.begin(), nums.end(),
    [](int& n) { n *= 2; });
// nums = {2, 4, 6, 8, 10}

4.7 std::all_of / std::any_of / std::none_of — Kiểm tra điều kiện

cpp
std::vector<int> scores = {85, 92, 78, 91, 88};

// Tất cả đều >= 70?
bool all_pass = std::all_of(scores.begin(), scores.end(),
    [](int s) { return s >= 70; });
// true

// Có ai đạt 100 không?
bool has_perfect = std::any_of(scores.begin(), scores.end(),
    [](int s) { return s == 100; });
// false

// Không ai dưới 50?
bool no_fail = std::none_of(scores.begin(), scores.end(),
    [](int s) { return s < 50; });
// true

📊 5. Bảng Tra Cứu Algorithm

Nhiệm vụAlgorithmComplexityHeader
Sắp xếpstd::sortO(n log n)<algorithm>
Sắp xếp ổn địnhstd::stable_sortO(n log²n)<algorithm>
Tìm phần tửstd::findO(n)<algorithm>
Tìm theo điều kiệnstd::find_ifO(n)<algorithm>
Đếmstd::count_ifO(n)<algorithm>
Biến đổistd::transformO(n)<algorithm>
Tổng hợpstd::accumulateO(n)<numeric>
Kiểm tra tất cảstd::all_ofO(n)<algorithm>
Kiểm tra bất kỳstd::any_ofO(n)<algorithm>
Kiểm tra không cóstd::none_ofO(n)<algorithm>
Xóa theo điều kiệnstd::remove_if + eraseO(n)<algorithm>
Giá trị lớn nhấtstd::max_elementO(n)<algorithm>
Sao chép có điều kiệnstd::copy_ifO(n)<algorithm>

🔄 6. Erase-Remove Idiom — Xóa Phần Tử Đúng Cách

Vấn đề: std::remove không thực sự xóa!

std::remove chỉ dồn các phần tử không bị xóa lên đầu, trả về iterator đến "vùng rác". Bạn phải gọi erase để thực sự xóa.

cpp
std::vector<int> v = {1, 0, 3, 0, 5, 0, 7};

// Bước 1: remove dồn phần tử — v trở thành {1, 3, 5, 7, ?, ?, ?}
auto new_end = std::remove(v.begin(), v.end(), 0);

// Bước 2: erase vùng rác
v.erase(new_end, v.end());
// v = {1, 3, 5, 7}

// Viết gọn trong 1 dòng — Erase-Remove Idiom
v.erase(std::remove(v.begin(), v.end(), 0), v.end());

Xóa theo điều kiện

cpp
// Xóa tất cả số âm
v.erase(
    std::remove_if(v.begin(), v.end(),
        [](int x) { return x < 0; }),
    v.end()
);

C++20: Đơn giản hơn nhiều!

cpp
// C++20 — std::erase / std::erase_if (trong <vector>)
std::vector<int> v = {1, 0, -3, 0, 5, -2, 7};

std::erase(v, 0);           // Xóa tất cả số 0
// v = {1, -3, 5, -2, 7}

std::erase_if(v, [](int x) { return x < 0; });  // Xóa số âm
// v = {1, 5, 7}

📌 C++20 std::erase_if

Nếu compiler hỗ trợ C++20, luôn dùng std::erase / std::erase_if. Ngắn gọn hơn, an toàn hơn, không cần nhớ idiom phức tạp.


🚀 7. C++20 Ranges — Tương Lai Của Algorithms

C++20 Ranges cho phép chain nhiều operations lại bằng pipe |, giống style functional programming.

cpp
#include <ranges>
#include <vector>
#include <string>

struct Order {
    std::string customer;
    double amount;
    std::string receipt() const {
        return customer + ": " + std::to_string(amount);
    }
};

std::vector<Order> orders = { /* ... */ };

// Pipeline: lọc → biến đổi → lấy kết quả
auto expensive_receipts = orders
    | std::views::filter([](const Order& o) {
        return o.amount > 1'000'000;
    })
    | std::views::transform([](const Order& o) {
        return o.receipt();
    });

// Lazy evaluation — chỉ tính khi duyệt
for (const auto& receipt : expensive_receipts) {
    std::cout << receipt << "\n";
}

So sánh: Traditional vs. Ranges

cpp
// ❌ Traditional — nhiều bước, nhiều container tạm
std::vector<Order> filtered;
std::copy_if(orders.begin(), orders.end(),
    std::back_inserter(filtered),
    [](const Order& o) { return o.amount > 1'000'000; });

std::vector<std::string> receipts;
std::transform(filtered.begin(), filtered.end(),
    std::back_inserter(receipts),
    [](const Order& o) { return o.receipt(); });

// ✅ Ranges — một pipeline, không container tạm
auto receipts = orders
    | std::views::filter([](const Order& o) { return o.amount > 1'000'000; })
    | std::views::transform([](const Order& o) { return o.receipt(); });

🎓 Ranges = Tương lai

Ranges là hướng đi của C++ algorithms. Nếu compiler hỗ trợ C++20, hãy bắt đầu dùng std::views cho code mới. Pipeline style rõ ràng, ít bug, và lazy (không tạo container trung gian).


💼 8. Ví Dụ Thực Tế: Order Analytics

Kết hợp mọi thứ đã học vào một bài toán business thực tế.

cpp
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>

struct Order {
    std::string customer;
    double amount;
    std::string date;       // "2024-01-15"
    std::string category;   // "electronics", "food", "clothing"

    std::string receipt() const {
        return "[" + date + "] " + customer + ": "
               + std::to_string(amount) + " VND";
    }
};

void analyze_orders(std::vector<Order> orders) {
    // 1. Tổng doanh thu
    double total_revenue = std::accumulate(
        orders.begin(), orders.end(), 0.0,
        [](double sum, const Order& o) { return sum + o.amount; });
    std::cout << "Tổng doanh thu: " << std::fixed << std::setprecision(0)
              << total_revenue << " VND\n";

    // 2. Đếm đơn hàng lớn (> 5 triệu)
    constexpr double BIG_ORDER_THRESHOLD = 5'000'000;
    int big_count = std::count_if(
        orders.begin(), orders.end(),
        [](const Order& o) { return o.amount > BIG_ORDER_THRESHOLD; });
    std::cout << "Đơn hàng lớn (>5M): " << big_count << "\n";

    // 3. Sắp xếp theo amount giảm dần
    std::sort(orders.begin(), orders.end(),
        [](const Order& a, const Order& b) {
            return a.amount > b.amount;
        });

    // 4. Top 3 đơn hàng lớn nhất
    std::cout << "\n--- Top 3 đơn hàng ---\n";
    int top_n = std::min(3, static_cast<int>(orders.size()));
    std::for_each(orders.begin(), orders.begin() + top_n,
        [](const Order& o) {
            std::cout << o.receipt() << "\n";
        });

    // 5. Tạo danh sách receipt cho đơn hàng electronics
    std::vector<std::string> elec_receipts;
    // Lọc + biến đổi — dùng copy_if sẽ không biến đổi được
    // → Kết hợp for_each với điều kiện, hoặc dùng ranges
    for (const auto& o : orders) {
        if (o.category == "electronics") {
            elec_receipts.push_back(o.receipt());
        }
    }
    // Hoặc C++20 Ranges:
    // auto elec_receipts = orders
    //     | std::views::filter([](const Order& o) {
    //           return o.category == "electronics"; })
    //     | std::views::transform(&Order::receipt);

    // 6. Kiểm tra: tất cả đơn hàng > 0?
    bool all_valid = std::all_of(
        orders.begin(), orders.end(),
        [](const Order& o) { return o.amount > 0; });
    std::cout << "\nTất cả đơn hàng hợp lệ: "
              << (all_valid ? "✅ Có" : "❌ Không") << "\n";

    // 7. Xóa đơn hàng bị hủy (amount = 0) — Erase-Remove
    orders.erase(
        std::remove_if(orders.begin(), orders.end(),
            [](const Order& o) { return o.amount <= 0; }),
        orders.end());
    std::cout << "Sau khi xóa đơn hủy: " << orders.size() << " đơn\n";
}

💼 Production Story

Một hệ thống e-commerce xử lý 50,000 đơn hàng/giây đã chuyển từ raw loops sang STL algorithms + lambdas. Kết quả: code giảm 40% số dòng, bug rate giảm 60%, và performance cải thiện 15% nhờ compiler tối ưu tốt hơn cho standard algorithms.


✏️ 9. Fast Exercise: Thay Thế Raw Loop

✏️ Bài tập nhanh

Hãy viết lại các raw loop sau bằng STL algorithm + lambda phù hợp.

Bài 1: Tìm phần tử đầu tiên > 100

cpp
// ❌ Raw loop — hãy thay bằng std::find_if
int result = -1;
for (int i = 0; i < nums.size(); ++i) {
    if (nums[i] > 100) {
        result = nums[i];
        break;
    }
}
💡 Xem đáp án
cpp
auto it = std::find_if(nums.begin(), nums.end(),
    [](int n) { return n > 100; });
int result = (it != nums.end()) ? *it : -1;

Bài 2: Tính tổng tất cả số chẵn

cpp
// ❌ Raw loop — hãy dùng kết hợp algorithms
int sum = 0;
for (int n : nums) {
    if (n % 2 == 0) {
        sum += n;
    }
}
💡 Xem đáp án
cpp
// Cách 1: accumulate với điều kiện trong lambda
int sum = std::accumulate(nums.begin(), nums.end(), 0,
    [](int acc, int n) { return n % 2 == 0 ? acc + n : acc; });

// Cách 2 (C++20 Ranges): filter rồi accumulate
// auto evens = nums | std::views::filter([](int n) { return n % 2 == 0; });
// int sum = std::accumulate(evens.begin(), evens.end(), 0);

Bài 3: Chuyển vector<string> thành vector<int> chứa độ dài

cpp
// ❌ Raw loop — hãy dùng std::transform
std::vector<std::string> words = {"Hello", "World", "C++"};
std::vector<int> lengths;
for (const auto& w : words) {
    lengths.push_back(w.size());
}
💡 Xem đáp án
cpp
std::vector<int> lengths;
std::transform(words.begin(), words.end(),
    std::back_inserter(lengths),
    [](const std::string& w) { return static_cast<int>(w.size()); });
// lengths = {5, 5, 3}

🐛 10. Spot The Bug!

🐛 Bug Hunt Challenge

Tìm bug trong đoạn code sau. Mỗi đoạn có ít nhất 1 lỗi.

Bug #1: Lambda capture

cpp
std::vector<std::function<int()>> counters;
for (int i = 0; i < 5; ++i) {
    counters.push_back([&i]() { return i; });
}
// Gọi counters[0](), counters[1]()... sẽ cho kết quả gì?
🔍 Phân tích bug

Bug: Capture i by reference [&i]. Sau khi vòng for kết thúc, i = 5 (hoặc undefined behavior vì i ra khỏi scope). Tất cả lambda đều trả về cùng một giá trị!

Fix: Capture by value [i]:

cpp
counters.push_back([i]() { return i; });
// Mỗi lambda giữ bản sao riêng: 0, 1, 2, 3, 4

Bug #2: Accumulate init value

cpp
std::vector<double> prices = {19.99, 29.99, 9.99};
auto total = std::accumulate(prices.begin(), prices.end(), 0);
std::cout << "Total: " << total << "\n";  // Mong đợi: 59.97
🔍 Phân tích bug

Bug: Init value 0int, nên accumulate trả về int. Mỗi lần cộng, kết quả bị truncate: 0 + 19.99 → 19, 19 + 29.99 → 48, 48 + 9.99 → 57.

Output thực tế: Total: 57 (không phải 59.97!)

Fix: Dùng 0.0:

cpp
auto total = std::accumulate(prices.begin(), prices.end(), 0.0);
// total = 59.97 ✅

Bug #3: Remove without erase

cpp
std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
std::remove(v.begin(), v.end(), 2);
std::cout << "Size: " << v.size() << "\n";  // Mong đợi: 4
🔍 Phân tích bug

Bug: std::remove không thay đổi size của container! Nó chỉ dồn phần tử lên đầu. v.size() vẫn là 7.

Fix: Dùng Erase-Remove Idiom:

cpp
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
std::cout << "Size: " << v.size() << "\n";  // 4 ✅

// Hoặc C++20:
// std::erase(v, 2);

11. Performance Notes

⚡ Hiệu năng cần biết

std::sort — Introsort

std::sort sử dụng introsort: bắt đầu với quicksort, chuyển sang heapsort nếu recursion quá sâu, và dùng insertion sort cho partition nhỏ. Kết quả:

  • Average: O(n log n)
  • Worst case: O(n log n) — không bị O(n²) như naive quicksort
  • Cache-friendly: Insertion sort cho partition nhỏ tận dụng cache tốt

Đừng tự viết sort. std::sort gần như luôn nhanh hơn.

Execution Policies (C++17)

cpp
#include <execution>

// Parallel sort — tận dụng multi-core
std::sort(std::execution::par, data.begin(), data.end());

// Parallel + vectorized
std::sort(std::execution::par_unseq, data.begin(), data.end());

Chỉ cần thêm một tham số, code của bạn tự động chạy song song. Đây là lý do dùng STL algorithms thay vì raw loops.


🚫 12. Anti-Patterns — Những Điều KHÔNG Nên Làm

☠️ Production Anti-Patterns

Những lỗi này xuất hiện trong code review hàng ngày. Tránh bằng mọi giá.

Anti-Pattern 1: Raw loop thay vì algorithm

cpp
// ❌ Viết loop tìm kiếm — không ai biết bạn đang tìm gì cho đến khi đọc xong
bool found = false;
for (size_t i = 0; i < users.size(); ++i) {
    if (users[i].id == target_id) {
        found = true;
        break;
    }
}

// ✅ Algorithm — ý định rõ ràng từ dòng đầu
auto it = std::find_if(users.begin(), users.end(),
    [target_id](const User& u) { return u.id == target_id; });
bool found = (it != users.end());

Anti-Pattern 2: Lambda quá phức tạp

cpp
// ❌ Lambda dài 15+ dòng — khó đọc, khó debug
std::sort(orders.begin(), orders.end(),
    [&config, &db, &logger](const Order& a, const Order& b) {
        auto priority_a = db.get_priority(a.customer);
        auto priority_b = db.get_priority(b.customer);
        if (priority_a != priority_b) {
            logger.log("Sorting by priority");
            return priority_a > priority_b;
        }
        // ... 10 dòng nữa ...
    });

// ✅ Extract thành named function
bool compare_orders(const Order& a, const Order& b) {
    // Logic rõ ràng, có thể unit test riêng
    if (a.priority != b.priority) return a.priority > b.priority;
    return a.amount > b.amount;
}
std::sort(orders.begin(), orders.end(), compare_orders);

Anti-Pattern 3: Capture [&] mặc định

cpp
// ❌ Capture everything by reference — nguy hiểm và không rõ ràng
auto process = [&]() {
    // Biến nào đang được dùng? Không ai biết!
    // Nếu lambda sống lâu hơn scope → crash
};

// ✅ Capture rõ ràng từng biến
auto process = [&orders, threshold]() {
    // Rõ ràng: dùng orders (by ref) và threshold (by value)
};

🎭 13. Scenario: Refactor Legacy Code

🎭 Tình huống thực tế

Bạn nhận được đoạn code legacy sau từ đồng nghiệp. Hãy refactor bằng STL algorithms + lambdas.

Code legacy:

cpp
// Legacy code — 25 dòng, khó đọc, dễ bug
std::vector<Product> get_report(const std::vector<Product>& products,
                                 double min_price) {
    std::vector<Product> result;
    for (int i = 0; i < products.size(); i++) {
        if (products[i].price >= min_price && products[i].in_stock) {
            result.push_back(products[i]);
        }
    }
    // Bubble sort (!)
    for (int i = 0; i < result.size(); i++) {
        for (int j = i + 1; j < result.size(); j++) {
            if (result[i].price > result[j].price) {
                Product temp = result[i];
                result[i] = result[j];
                result[j] = temp;
            }
        }
    }
    double total = 0;
    for (int i = 0; i < result.size(); i++) {
        total += result[i].price;
    }
    std::cout << "Total: " << total << "\n";
    return result;
}

Refactored:

cpp
// Modern C++ — 12 dòng, rõ ý, nhanh hơn
std::vector<Product> get_report(const std::vector<Product>& products,
                                 double min_price) {
    std::vector<Product> result;
    std::copy_if(products.begin(), products.end(),
        std::back_inserter(result),
        [min_price](const Product& p) {
            return p.price >= min_price && p.in_stock;
        });

    std::sort(result.begin(), result.end(),
        [](const Product& a, const Product& b) {
            return a.price < b.price;
        });

    double total = std::accumulate(result.begin(), result.end(), 0.0,
        [](double sum, const Product& p) { return sum + p.price; });
    std::cout << "Total: " << total << "\n";

    return result;
}

Cải thiện:

  • copy_if thay vì loop + if → rõ ý định "lọc"
  • std::sort (introsort O(n log n)) thay vì bubble sort O(n²)
  • accumulate thay vì loop tính tổng → không thể sai index
  • ✅ Ít dòng hơn, ít chỗ để bug ẩn nấp

14. Module 3 Complete — Checkpoint

🎉 Module 3: STL — Standard Weapons hoàn tất!

Bạn đã nắm vững:

BàiNội dungKỹ năng
07 — STL Containersvector, map, set, unordered_mapChọn container đúng cho bài toán
08 — Algorithms & Lambdas (bài này)sort, find, transform, lambdasViết code ngắn gọn, biểu cảm

Bạn đã có thể:

  • ✅ Chọn container phù hợp (Module 3, Bài 7)
  • ✅ Dùng STL algorithms thay raw loops
  • ✅ Viết lambdas với capture semantics đúng
  • ✅ Áp dụng Erase-Remove idiom
  • ✅ Biết C++20 Ranges preview

Tiếp theo: Phụ lục — Memory Mistakes & STL vs Manual — tổng hợp những lỗi memory phổ biến và so sánh STL với cách làm thủ công.


🧠 Quiz

Câu 1: Lambda capture [&x] nghĩa là gì?

  • [ ] A) Capture biến x by value (tạo bản sao)
  • [x] B) Capture biến x by reference (tham chiếu đến biến gốc)
  • [ ] C) Capture tất cả biến by reference
  • [ ] D) Capture địa chỉ của x dưới dạng pointer

💡 Giải thích: [&x] capture biến x bằng tham chiếu — lambda truy cập trực tiếp biến gốc. Thay đổi x trong lambda sẽ thay đổi biến bên ngoài. Cẩn thận: nếu lambda sống lâu hơn x, bạn sẽ có dangling reference!

Câu 2: std::remove(v.begin(), v.end(), 42) làm gì?

  • [ ] A) Xóa tất cả phần tử có giá trị 42 và giảm v.size()
  • [x] B) Dồn các phần tử khác 42 lên đầu, trả về iterator đến "vùng rác" — v.size() KHÔNG đổi
  • [ ] C) Đánh dấu các phần tử 42 là deleted, cần gọi v.compact() sau
  • [ ] D) Ném exception nếu không tìm thấy 42

💡 Giải thích: std::remove là algorithm hoạt động trên range, không biết về container. Nó dồn phần tử "giữ lại" lên đầu và trả về iterator đến phần tử đầu tiên không cần giữ. Phải kết hợp với v.erase(it, v.end()) để thực sự xóa — đây là Erase-Remove Idiom. C++20 có std::erase(v, 42) tiện hơn.

Câu 3: Đoạn code sau in ra giá trị gì?

cpp
std::vector<double> v = {1.7, 2.3, 3.9};
auto result = std::accumulate(v.begin(), v.end(), 0);
std::cout << result;
  • [ ] A) 7.9
  • [x] B) 6
  • [ ] C) 7
  • [ ] D) Compilation error

💡 Giải thích: Init value 0 có kiểu int, nên accumulate trả về int. Mỗi lần cộng: 0 + 1.7 → 1 (truncate), 1 + 2.3 → 3, 3 + 3.9 → 6. Kết quả là 6, không phải 7.9! Fix: dùng 0.0 làm init value để giữ kiểu double.