--- id: sorting-cpp title: "More on Sorting in C++" author: Siyong Huang, Michael Cao, Darren Yao, Benjamin Qi prerequisites: - Bronze - Introduction to Graphs - Silver - Sorting with Custom Comparators description: More information about sorting with custom comparators in C++. Some overlap with the previous module. --- import { Problem } from "../models"; export const metadata = { problems: { sample: [ new Problem("Silver", "Wormhole Sort", "992", "Normal", false, [], ""), ], } }; ## Additional Reading ## Example: Wormhole Sort (Silver) There are multiple ways to solve this problem. We won't discuss the full solution here, but all of them start by sorting the edges in nondecreasing order of weight. - If we only stored the edge weights and sorted them, we would have a sorted list of edge weights, but it would be impossible to tell which weights corresponded to which edges. - However, if we create a class representing the edges and define a custom comparator to sort them by weight, we can sort the edges in ascending order while also keeping track of their endpoints. ## Comparators for Sorting There are 2 main ways to have a custom comparator in c++. ### Overloading Operator [StackOverflow: Why const T&?](https://stackoverflow.com/questions/11805322/why-should-i-use-const-t-instead-of-const-t-or-t) - Pro: - This is the easiest to implement - Easy to work with STL - Con: - Only works for objects (not primitives) - Only supports two types of comparisons (less than (<) and greater than (>)) ```cpp #include using namespace std; int randint(int low, int high) {return low+rand()%(high-low);} struct Foo { int Bar; Foo(int _Bar=-1):Bar(_Bar){} bool operator < (const Foo& foo2) const {return Bar < foo2.Bar;} }; const int N = 8; int main() { srand(69); Foo a[N]; for(int i=0;i using namespace std; struct Edge { int a,b,w; friend bool operator<(const Edge& x, const Edge& y) { return x.w < y.w; } }; // a different way to write less than int main() { int M = 4; vector v; for (int i = 0; i < M; ++i) { int a,b,w; cin >> a >> b >> w; v.push_back({a,b,w}); } sort(begin(v),end(v)); for (Edge e: v) cout << e.a << " " << e.b << " " << e.w << "\n"; } /* Input: 1 2 9 1 3 7 2 3 10 2 4 3 */ /* Output: 2 4 3 1 3 7 1 2 9 2 3 10 */ ``` ### Function - Pro: - Works for both objects and primitives - Supports many different comparators for the same object - Con: - More difficult to implement - Extra care needs to be taken to support STL We can also use [lambda expressions](https://www.geeksforgeeks.org/lambda-expression-in-c/) in C++11 or above. ```cpp #include using namespace std; int randint(int low, int high) {return low+rand()%(high-low);} struct Foo { int Bar; Foo(int _Bar=-1):Bar(_Bar){} }; const int N = 8; Foo a[N]; bool cmp1(Foo foo1, Foo foo2) {return foo1.Bar < foo2.Bar;} function cmp2 = [](Foo foo1, Foo foo2) {return foo1.Bar < foo2.Bar;}; // lambda expression // bool(Foo,Foo) means that the function takes in two parameters of type Foo and returns bool // "function"" can be replaced with "auto" int main() { srand(69); printf("--- Method 1 ---\n"); for(int i=0;i using namespace std; struct Edge { int a,b,w; }; int main() { int M = 4; vector v; for (int i = 0; i < M; ++i) { int a,b,w; cin >> a >> b >> w; v.push_back({a,b,w}); } sort(begin(v),end(v),[](const Edge& x, const Edge& y) { return x.w < y.w; }); for (Edge e: v) cout << e.a << " " << e.b << " " << e.w << "\n"; } ``` ### Comparators for STL Operator overloading works as expected for using in STL. If you are sorting elements in reverse order, you can use the STL [greater](https://en.cppreference.com/w/cpp/utility/functional/greater) comparator instead. For function comparators, some extra care needs to be taken: ```cpp struct foo { //members }; auto cmp1 = [](const foo& a, const foo& b){return /*comparator function*/;}; //This way is shorter to write, but don't forget to pass the comparator as a parameter in the constructor! //If you need to create an array of the objects, I would either use pointers of the method shown below set Set1(cmp1); priority_queue, decltype(cmp1)> pq1(cmp1); //Side Note: priority queue is sorted in REVERSE order (largest elements are first) map Map1(cmp1); struct cmp2 { bool operator () (const foo& a, const foo& b){return /*comparator function*/;} }; //Here you do not need to pass the comparator as a parameter //This makes it easier to create arrays of stl objects set Set2; priority_queue, cmp2> pq2; map Map2; set Set2b[100];//array of sets with the comparator ``` ### Example of Comparators for Primitives Since you cannot overload operators for primitives, you must use custom comparators. ```cpp #include using namespace std; const int N = 8; int a[N], b[N] = {4,8,2,3,4,1,2,4}; int main() { printf("--- Comparator 1 ---\n"); iota(a, a+N, 0); sort(a, a+N, greater()); //sort a in decreasing order for(int i=0;iy);}); //sort a by increasing values of b[i], breaking ties by decreasing index for(int i=0;i`. In the above example, instead of creating a `struct`, we can simply declare an array of pairs. The sort function automatically uses the first element of the pair for comparison and the second element as a secondary point of comparison: ```cpp pair arr[N]; int main() { sort(arr, arr+N); } ``` For Wormhole Sort, there is no need to create a custom comparator: ```cpp #include using namespace std; #define f first #define s second int main() { int M = 4; vector>> v; for (int i = 0; i < M; ++i) { int a,b,w; cin >> a >> b >> w; v.push_back({w,{a,b}}); } sort(begin(v),end(v)); for (auto e: v) cout << e.s.f << " " << e.s.s << " " << e.f << "\n"; } ```