217 lines
6.8 KiB
Text
217 lines
6.8 KiB
Text
|
---
|
||
|
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<Person>{
|
||
|
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<int[]>`. 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<int[]>{
|
||
|
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
|
||
|
|
||
|
<!-- Tested -->
|
||
|
```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:
|
||
|
|
||
|
<!-- Reasonably Tested -->
|
||
|
```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<foo, decltype(cmp1)> Set1(cmp1);
|
||
|
priority_queue<foo, vector<foo>, decltype(cmp1)> pq1(cmp1);
|
||
|
//Side Note: priority queue is sorted in REVERSE order (largest elements are first)
|
||
|
map<foo, bar, decltype(cmp1)> 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<foo, cmp2> Set2;
|
||
|
priority_queue<foo, vector<foo>, cmp2> pq2;
|
||
|
map<foo, bar, cmp2> Map2;
|
||
|
set<foo, cmp2> 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.
|
||
|
|
||
|
<!-- Tested -->
|
||
|
```cpp
|
||
|
#include <bits/stdc++.h>
|
||
|
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<int>());
|
||
|
//sort a in decreasing order
|
||
|
for(int i=0;i<N;++i) printf("a[%d] = %d\n", i, a[i]);
|
||
|
printf("--- Comparator 2 ---\n");
|
||
|
iota(a, a+N, 0);
|
||
|
sort(a, a+N, [](int x, int y){return b[x]<b[y]||(b[x]==b[y]&&x>y);});
|
||
|
//sort a by increasing values of b[i], breaking ties by decreasing index
|
||
|
for(int i=0;i<N;++i) printf("a[%d] = %d: b[%d] = %d\n", i, a[i], a[i], b[a[i]]);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Output:
|
||
|
--- Comparator 1 ---
|
||
|
a[0] = 7
|
||
|
a[1] = 6
|
||
|
a[2] = 5
|
||
|
a[3] = 4
|
||
|
a[4] = 3
|
||
|
a[5] = 2
|
||
|
a[6] = 1
|
||
|
a[7] = 0
|
||
|
--- Comparator 2 ---
|
||
|
a[0] = 5: b[5] = 1
|
||
|
a[1] = 6: b[6] = 2
|
||
|
a[2] = 2: b[2] = 2
|
||
|
a[3] = 3: b[3] = 3
|
||
|
a[4] = 7: b[7] = 4
|
||
|
a[5] = 4: b[4] = 4
|
||
|
a[6] = 0: b[0] = 4
|
||
|
a[7] = 1: b[1] = 8
|
||
|
*/
|
||
|
```
|
||
|
|
||
|
Comments: Comparator 1 sorts array $a$ in decreasing order. Comparator 2 sorts array $a$ in increasing $b[a[i]]$ value, breaking ties by placing the greater index first.
|
||
|
|
||
|
## Problems
|
||
|
|
||
|
<problems-list problems={metadata.problems.general} />
|