Comparators in STL
A comparator in C++ is a function or function object that defines a custom sorting order. Comparators are commonly used with standard library algorithms like sort
, qsort
, priority_queue
, and others, where the default ordering (usually ascending) is not sufficient, and a custom sorting criterion is needed.
What is a Comparator?
In the context of C++, a comparator is a binary predicate, which means it is a function or function object that takes two arguments (often of the same type) and returns a boolean value. The return value indicates whether the first argument should be ordered before the second. The criteria for ordering can be anything: numerical order, lexicographical order, custom-defined order, etc. For sorting purposes, the function usually returns true
if the first argument should come before the second, according to some custom criteria.
Return Value:
true
: Indicates that the first argument should be ordered before the second. In simple if you don't want to do swap return true.false
: Indicates that the first argument should not be ordered before the second (i.e., it could be equal or greater depending on the logic). In simple if you want to do swap return false.
Why Use Comparators?
- Custom Sorting: Allows you to define how elements are compared, enabling sorting based on specific criteria, such as descending order, sorting by multiple attributes, or sorting complex data structures.
- Flexibility: Provides the flexibility to reuse standard algorithms with different comparison logic without rewriting the algorithms themselves.
Types of Comparators
- Function Pointers
- Lambda Expressions
- Function Objects (Functors)
Let's explore each type with examples.
1️⃣ Function Pointers:
A function pointer can be used as a comparator by defining a function that returns a boolean value indicating the order of two elements.
Example: Sorting Integers in Descending Order:
#include <iostream>
#include <vector>
#include <algorithm>
// Function pointer comparator
bool compareDescending(int a, int b) {
return a > b; // Sort in descending order
}
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 3};
// Using sort with a function pointer comparator
std::sort(numbers.begin(), numbers.end(), compareDescending);
for (int num : numbers) {
std::cout << num << " "; // Output: 8 5 3 2 1
}
std::cout << std::endl;
return 0;
}
Explanation of Comparator Function Logic:
- Function Definition:
bool compareDescending(int a, int b)
:- This function takes two integers
a
andb
as arguments.
- This function takes two integers
- Comparison Logic:
- The function returns
true
ifa > b
. - This means that when
std::sort
calls this function, ifa
is greater thanb
, it will placea
beforeb
in the sorted order.
- The function returns
- Sorting Execution:
std::sort
uses this comparator function to determine the order of every pair of elements in the vector.- For every pair
(a, b)
, it checks ifcompareDescending(a, b)
returnstrue
orfalse
:- If
true
,a
is placed beforeb
. - If
false
,a
is placed afterb
.
- If
Detailed Step-by-Step Process
Let's walk through the sorting process step-by-step with the vector {5, 2, 8, 1, 3}
.
- Initial State:
- The vector is unsorted:
{5, 2, 8, 1, 3}
.
- The vector is unsorted:
- First Iteration (Comparing all elements):
- Compare 5 and 2:
compareDescending(5, 2)
returnstrue
(5 > 2). No change needed. - Compare 2 and 8:
compareDescending(2, 8)
returnsfalse
(2 < 8). Swap 2 and 8. Vector becomes{5, 8, 2, 1, 3}
. - Compare 2 and 1:
compareDescending(2, 1)
returnstrue
(2 > 1). No change needed. - Compare 1 and 3:
compareDescending(1, 3)
returnsfalse
(1 < 3). Swap 1 and 3. Vector becomes{5, 8, 2, 3, 1}
.
- Compare 5 and 2:
- Second Iteration (Comparing after the first iteration):
- Compare 5 and 8:
compareDescending(5, 8)
returnsfalse
(5 < 8). Swap 5 and 8. Vector becomes{8, 5, 2, 3, 1}
. - Compare 5 and 2:
compareDescending(5, 2)
returnstrue
(5 > 2). No change needed. - Compare 2 and 3:
compareDescending(2, 3)
returnsfalse
(2 < 3). Swap 2 and 3. Vector becomes{8, 5, 3, 2, 1}
.
- Compare 5 and 8:
- Subsequent Iterations:
- Continue comparing and swapping based on the comparator function until the entire vector is sorted.
- Final State:
- The vector is sorted in descending order:
{8, 5, 3, 2, 1}
.
- The vector is sorted in descending order:
2️⃣ Lambda Expressions:
Lambda expressions provide a concise way to define comparators inline without needing separate functions. They are often preferred for their simplicity and readability.
Example: Sorting Strings by Length:
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
int main() {
std::vector<std::string> words = {"apple", "banana", "kiwi", "strawberry"};
// Using sort with a lambda expression as a comparator
std::sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) {
return a.length() < b.length(); // Sort by string length in ascending order
});
for (const std::string& word : words) {
std::cout << word << " "; // Output: kiwi apple banana strawberry
}
std::cout << std::endl;
return 0;
}
Explanation:
- Lambda Expression:
[](const std::string& a, const std::string& b) { return a.length() < b.length(); }
defines a lambda function that takes two strings and returnstrue
if the first string is shorter than the second.
3️⃣ Function Objects (Functors)
A function object or functor is a class or struct that overloads the operator()
to allow an instance of the class to be called as a function. Functors are more flexible than function pointers and lambdas because they can hold state.
Example: Sorting by Custom Rule with a Functor:
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
// Functor comparator
struct CompareByLastChar {
bool operator()(const std::string& a, const std::string& b) const {
return a.back() < b.back(); // Compare based on the last character of the strings
}
};
int main() {
std::vector<std::string> words = {"apple", "banana", "kiwi", "strawberry"};
// Using sort with a functor comparator
std::sort(words.begin(), words.end(), CompareByLastChar());
for (const std::string& word : words) {
std::cout << word << " "; // Output: banana apple kiwi strawberry
}
std::cout << std::endl;
return 0;
}
Explanation:
- Functor: The struct
CompareByLastChar
defines a custom sorting criterion based on the last character of each string. - Operator Overloading: The
operator()
function is overloaded to compare the last character of two strings.