Banyak struktur data STL butuh comparator untuk menentukan urutan elemen: std::sort mengurutkan, std::priority_queue menentukan prioritas, std::set/std::map menjaga key terurut. Untuk tipe primitif, default std::less<T> cukup. Tetapi untuk struct user-defined atau urutan non-default (descending, multi-kriteria, density-based), kita harus menyediakan comparator sendiri. Pada Chapter ini kita akan membandingkan tiga pendekatan — operator< overload, functor comparator, dan lambda — serta mendemonstrasikan comparator stateful dan strategi anti-overflow via perkalian silang.
Tiga Pendekatan Comparator #
| Pendekatan | Kapan dipakai | Kelebihan | Kekurangan |
|---|---|---|---|
operator< member of T | Urutan “natural” default | Otomatis dipakai semua STL; ringkas | Hanya satu urutan default |
Functor struct Cmp | Reusable, dipakai banyak tempat | Efisien (inline); parameter template | Verbose |
Lambda [](a,b) {...} | Sekali pakai, inline | Ringkas; capture fleksibel | Tidak reusable tanpa decltype |
Mekanisme: Strict Weak Ordering #
Semua comparator STL wajib strict weak ordering: irreflexive (cmp(a,a) == false), asymmetric, transitive, transitivity of equivalence. Untuk priority_queue, cmp(a, b) == true berarti a harus berada di bawah b — sehingga std::greater<T> menghasilkan min-heap (elemen kecil naik ke top).
Kode: Enam Skenario Comparator #
#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <algorithm>
#include <string>
#include <functional>
// 1) Comparator untuk std::sort — lambda vs functor
struct Point {
int x, y;
};
// 2) operator< overload — default ordering untuk struct
struct Task {
int priority;
std::string name;
bool operator<(const Task& o) const {
// priority rendah = didahulukan (min-priority)
if (priority != o.priority) return priority < o.priority;
return name < o.name;
}
};
// 3) Functor comparator — fleksibel, reusable
struct TaskByPriorityDesc {
bool operator()(const Task& a, const Task& b) const {
if (a.priority != b.priority) return a.priority > b.priority; // desc
return a.name < b.name;
}
};
// 4) Custom comparator untuk std::priority_queue
struct Item {
int weight;
int value;
};
struct ItemByValueDensityDesc {
bool operator()(const Item& a, const Item& b) const {
// value/weight descending — gunakan perkalian untuk hindari float
return (long long)a.value * b.weight < (long long)b.value * a.weight;
}
};
// 5) Comparator stateful
struct ThresholdCmp {
int threshold;
explicit ThresholdCmp(int t) : threshold(t) {}
bool operator()(int a, int b) const {
// Elemen >= threshold duluan, lalu naik
bool a_hi = a >= threshold, b_hi = b >= threshold;
if (a_hi != b_hi) return a_hi > b_hi; // hi lebih dulu
return a < b;
}
};
int main() {
// 1) std::sort dengan lambda
std::vector<Point> pts = {{3, 1}, {1, 5}, {3, 0}, {2, 2}};
std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) {
if (a.x != b.x) return a.x < b.x;
return a.y < b.y;
});
std::cout << "sort by (x,y) asc:\n";
for (const auto& p : pts) std::cout << " (" << p.x << ',' << p.y << ")\n";
// 2) Default operator< — tidak perlu comparator eksplisit
std::vector<Task> tasks = {
{3, "deploy"}, {1, "fix-bug"}, {2, "patch"}, {1, "alert"}
};
std::sort(tasks.begin(), tasks.end());
std::cout << "tasks sorted by operator< (priority asc, name asc):\n";
for (const auto& t : tasks) std::cout << " P" << t.priority << ' ' << t.name << '\n';
// 3) Functor untuk urutan berbeda
std::sort(tasks.begin(), tasks.end(), TaskByPriorityDesc{});
std::cout << "tasks sorted desc by priority:\n";
for (const auto& t : tasks) std::cout << " P" << t.priority << ' ' << t.name << '\n';
// 4) priority_queue custom
std::priority_queue<Item, std::vector<Item>, ItemByValueDensityDesc> pq;
pq.push({10, 60});
pq.push({5, 50});
pq.push({20, 40});
std::cout << "pq by value density desc (value/weight):\n";
while (!pq.empty()) {
const auto& it = pq.top();
std::cout << " w=" << it.weight << " v=" << it.value
<< " ratio=" << (double)it.value / it.weight << '\n';
pq.pop();
}
// 5) std::set dengan comparator custom
std::set<int, ThresholdCmp> s{ThresholdCmp{5}};
for (int x : {3, 8, 1, 10, 4, 7, 2}) s.insert(x);
std::cout << "set (>=5 duluan, lalu naik):\n";
for (int x : s) std::cout << " " << x << '\n';
// 6) Comparator stateful dengan lambda capture
int thr = 5;
std::vector<int> v = {3, 8, 1, 10, 4, 7, 2};
std::sort(v.begin(), v.end(), [thr](int a, int b) {
bool a_hi = a >= thr, b_hi = b >= thr;
if (a_hi != b_hi) return a_hi > b_hi;
return a < b;
});
std::cout << "vector (>=5 duluan, lalu naik): ";
for (int x : v) std::cout << x << ' ';
std::cout << '\n';
return 0;
}Output yang Dihasilkan:
sort by (x,y) asc:
(1,5)
(2,2)
(3,0)
(3,1)
tasks sorted by operator< (priority asc, name asc):
P1 alert
P1 fix-bug
P2 patch
P3 deploy
tasks sorted desc by priority:
P3 deploy
P2 patch
P1 alert
P1 fix-bug
pq by value density desc (value/weight):
w=5 v=50 ratio=10
w=10 v=60 ratio=6
w=20 v=40 ratio=2
set (>=5 duluan, lalu naik):
7
8
10
1
2
3
4
vector (>=5 duluan, lalu naik): 7 8 10 1 2 3 4 Bedah Kode Baris-per-Baris #
- Lambda comparator
[](const Point& a, const Point& b): comparator inline untukstd::sort. Multi-kriteria: bandingkanxdulu, laluy. Lambda tanpa capture bisa dikonversi ke function pointer. Task::operator<: default ordering untukTask. Priority naik (1, 2, 3) lalu name ascending. Saatstd::sort(tasks.begin(), tasks.end())tanpa comparator eksplisit, STL otomatis memakaioperator<.TaskByPriorityDescfunctor: comparator alternatif — priority descending. Dipakai viastd::sort(tasks.begin(), tasks.end(), TaskByPriorityDesc{}). Satu struct bisa punya beberapa comparator alternatif — fleksibel.ItemByValueDensityDescuntukpriority_queue: menghitung densityvalue/weighttanpa pembagian — pakai perkalian silang(a.value * b.weight) < (b.value * a.weight). Mencegah pembulatan float dan overflowint(cast kelong long). Hasil: item dengan density tertinggi (10, 6, 2) di top.ThresholdCmpcomparator stateful: functor dengan memberthreshold. Dibangun viaThresholdCmp{5}. Mengembalikantruebilaaharus dianggap “lebih kecil” — elemen ≥ threshold didahulukan, lalu naik per nilai.std::set<int, ThresholdCmp> s{ThresholdCmp{5}}:std::setmenerima comparator sebagai parameter template kedua. Konstruktor menerima instance comparator (dibutuhkan untuk comparator stateful). Iterasi set:7, 8, 10, 1, 2, 3, 4— elemen ≥ 5 duluan, lalu sisanya naik.- Lambda dengan capture
[thr]: lambda stateful untukstd::sort. Sama perilakunya denganThresholdCmp. Untukpriority_queue, lambda butuhdecltypedan konstruktor — lebih verbose daripada functor.
Pemilihan Cara #
| Skenario | Cara terbaik |
|---|---|
| Urutan natural jelas (angka, tanggal, nama) | operator< member |
| Satu urutan default + beberapa alternatif | operator< + functor untuk alternatif |
| Comparator sekali pakai di satu fungsi | Lambda |
| Comparator dipakai banyak tempat | Functor |
Comparator untuk priority_queue/set/map | Functor (parameter template) |
| Comparator stateful | Functor dengan member atau lambda capture |
Pola Comparator Multi-Kriteria dengan std::tie #
Idiomatik untuk banyak field:
struct Employee { int dept; int salary; std::string name; };
auto cmp = [](const Employee& a, const Employee& b) {
return std::tie(a.dept, a.salary, a.name)
< std::tie(b.dept, b.salary, b.name);
};std::tie membuat tuple referensi lalu membandingkan secara lexicographic. Lebih ringkas dan minim bug dibanding penulisan if (a.x != b.x) return a.x < b.x; berulang.
Comparator untuk priority_queue — greater vs Custom #
Default std::priority_queue<T> memakai std::less<T> → max-heap. Untuk min-heap, pakai std::greater<T>:
std::priority_queue<int, std::vector<int>, std::greater<int>> minpq;Untuk struct dengan comparator custom, gunakan functor sebagai parameter template ketiga:
std::priority_queue<Item, std::vector<Item>, ItemByValueDensityDesc> pq;Ringkasan #
- Tiga pendekatan:
operator<member, functor, lambda. operator<otomatis dipakai semua STL; gunakan bila urutan natural jelas.- Functor reusable dan efisien (inline); cocok untuk parameter template
set/map/priority_queue. - Lambda sekali pakai; bisa capture state untuk comparator stateful.
- Untuk komparasi rasio, pakai perkalian silang (
a*d < c*b) — hindari pembagian float. - Multi-kriteria:
std::tie(a.x, a.y, a.z) < std::tie(b.x, b.y, b.z). - Comparator wajib strict weak ordering (irreflexive, asymmetric, transitive).