--- id: sorting-custom-2 title: "Sorting with Custom Comparators Pt 2" author: Darren Yao, Siyong Huang, Michael Cao, Benjamin Qi prerequisites: - Bronze - Pairs & Tuples in C++ - Bronze - Introduction to Data Structures description: Both Java and C++ have built-in functions for sorting. However, if we use custom objects, or if we want to sort elements in a different order, then we'll need to use a custom comparator. --- ## Sorting by Multiple Criteria Now, suppose we wanted to sort a list of `Person`s in ascending order, primarily by height and secondarily by weight. We can do this quite similarly to how we handled sorting by one criterion earlier. What the comparator function needs to do is to compare the weights if the heights are equal, and otherwise compare heights, as that's the primary sorting criterion. ### C++ ```cpp bool cmp(Person a, Person b) { if(a.height == b.height) { return a.weight < b.weight; } return a.height < b.height; } int main() { sort(arr, arr+N, cmp); // sorts the array in ascending order by height and then weight if the heights are equal } ``` Sorting with an arbitrary number of criteria is done similarly. ### Java ```java static class Person implements Comparable{ int height, weight; public Person(int h, int w){ height = h; weight = w; } public int compareTo(Person p){ if(height == p.height){ return Integer.compare(weight, p.weight); } else { return Integer.compare(height, p.height); } } } ``` Sorting with an arbitrary number of criteria is done similarly. An alternative way of representing custom objects is with arrays. Instead of using a custom object to store data about each person, we can simply use `int[]`, where each `int` array is of size 2, and stores pairs of {height, weight}, probably in the form of a list like `ArrayList`. Since arrays aren't objects in the usual sense, we need to use `Comparator`. Example for sorting by the same two criteria as above: ```java static class Comp implements Comparator{ public int compare(int[] a, int[] b){ if(a[0] == b[0]){ return Integer.compare(a[1], b[1]); } else { return Integer.compare(a[0], b[0]); } } } ``` I don't recommend using arrays to represent objects mostly because it's confusing, but it's worth noting that some competitors do this. ### Python Operator Overloading can be used ```py import random class Foo: def __init__(self, _Bar, _Baz): self.Bar,self.Baz = _Bar,_Baz def __str__(self): return "Foo({},{})".format(self.Bar, self.Baz) def __lt__(self, o): # sort by increasing Bar, breaking ties by decreasing Baz if self.Bar != o.Bar: return self.Bar < o.Bar if self.Baz != o.Baz: return self.Baz > o.Baz return False a = [] for i in range(8): a.append(Foo(random.randint(1, 9), random.randint(1, 9))) print(*a) print(*sorted(a)) ``` Output: ``` Foo(1,2) Foo(3,2) Foo(6,6) Foo(9,7) Foo(8,7) Foo(8,9) Foo(6,9) Foo(9,8) Foo(1,2) Foo(3,2) Foo(6,9) Foo(6,6) Foo(8,9) Foo(8,7) Foo(9,8) Foo(9,7) ``` A custom comparator works as well - Lambdas aren't shown here because they are typically used as one-liners ```py import random from functools import cmp_to_key class Foo: def __init__(self, _Bar, _Baz): self.Bar,self.Baz = _Bar,_Baz def __str__(self): return "Foo({},{})".format(self.Bar, self.Baz) a = [] for i in range(8): a.append(Foo(random.randint(1, 9), random.randint(1, 9))) print(*a) def cmp(foo1, foo2): # Increasing Bar, breaking ties with decreasing Baz if foo1.Bar != foo2.Bar: return -1 if foo1.Bar < foo2.Bar else 1 if foo1.Baz != foo2.Baz: return -1 if foo1.Baz > foo2.Baz else 1 return 0 print(*sorted(a, key=cmp_to_key(cmp))) # Python automatically sorts tuples in increasing order with priority to the leftmost element # You can sort objects by its mapping to a tuple of its elements # The following sorts Foo by increasing Bar values, breaking ties with increasing Baz value print(*sorted(a, key=lambda foo: (foo.Bar, foo.Baz))) ``` Output: ``` Foo(1,2) Foo(3,2) Foo(6,6) Foo(9,7) Foo(8,7) Foo(8,9) Foo(6,9) Foo(9,8) Foo(1,2) Foo(3,2) Foo(6,9) Foo(6,6) Foo(8,9) Foo(8,7) Foo(9,8) Foo(9,7) Foo(1,2) Foo(3,2) Foo(6,6) Foo(6,9) Foo(8,7) Foo(8,9) Foo(9,7) Foo(9,8) ``` ## Comparators for C++ 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