Custom Sorting
| Resources | |||||
|---|---|---|---|---|---|
| IUSACO | partially based off this | ||||
| CPH | short overview | ||||
Sorting can apply to more than just numbers. For example, many solutions to Wormhole Sort involve first sorting the list of edges by their weight.
For example, the sample case gives us the following edges:
1 2 9 1 3 7 2 3 10 2 4 3
After sorting, it should look like
2 4 3 1 3 7 1 2 9 2 3 10
We will describe several ways to do this below.
There's multiple ways we can do this. In Python, we can use a list of lists:
edge_num = 4edges = []for _ in range(edge_num):a, b, width = [int(i) for i in input().split()]edges.append((width, a, b))edges.sort()for e in edges:print(f"({e[1]}, {e[2]}): {e[0]}")
Another option would be a Tuple[int, Tuple[int]],
but that would make indexing more convoluted.
Comparators
Most sorting functions rely on moving objects with a lower value in front of objects with a higher value if sorting in ascending order, and vice versa if in descending order. This is done through comparing two objects at a time.
Key Function
Python's sorting functions take a key argument that takes in
an object and returns a comparable datatype like an int.
class Edge:def __init__(self, a: int, b: int, width: int):self.a = aself.b = bself.width = widthedge_num = 4edges = [Edge(*[int(i) for i in input().split()]) for _ in range(edge_num)]edges.sort(key=lambda e: e.width)for e in edges:print(f"({e.a}, {e.b}): {e.width}")
Comparator
A less idiomatic but still supported way to sort objects in Python is with comparators that return the relative order of two objects.
Let's call this comparator compare, and the objects it compares x and y.
For compare to be valid, the following must be true:
- If
xis less thany, return-1. - If
xis greater thany, return1. - If the two are equal, return
0. - If
compare(x, y) > 0, thencompare(y, x) < 0. (antisymmetry) - If
compare(x, y) > 0andcompare(y, z) > 0, thencompare(x, z) > 0. (transitivity)
Python's sorting functions don't take comparators directly.
We have to convert them to something key can take with
functools.cmp_to_key
like so:
from functools import cmp_to_keyclass Edge:def __init__(self, a: int, b: int, width: int):self.a = aself.b = bself.width = width
See this SO post
for an explanation of how cmp_to_key works.
Variations
Sorting in Descending Order
In Python, we can pass the parameter
reverse=True to the sort or sorted function.
Sorting by Multiple Criteria
Now, suppose we wanted to sort a list of Edges in ascending order, primarily
by width and secondarily by first vertex (a). 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 weights are not equal, and
otherwise compare first vertices.
In Python, tuples have a natural order based on their elements in order. We can take advantage of this to write a comparator:
from functools import cmp_to_keyclass Edge:def __init__(self, a: int, b: int, width: int):self.a = aself.b = bself.width = width
Try this slightly modified version of the sample input:
2 2 7 1 3 7 2 3 10 2 4 3
While edges with width and will still assume the same positions, edges and will swap in the final ordering with the inclusion of the second criterion.
Coordinate Compression
Coordinate compression describes the process of mapping each value in a list to its index if that list was sorted. For example, the list would be compressed to . Notice that is the least value in the first list, so it becomes , and is the greatest value, so it becomes , the largest index in the list.
When we have values from a large range, but we only care about their relative order (for example, if we have to know if one value is above another), coordinate compression is a simple way to help with implementation. For example, if we have a set of integers ranging from to , we can't use them as array indices because we'd have to create an array of size , which would cause an MLE verdict. However, if there are only such integers, we can coordinate-compress their values, which guarantees that the values will all be in the range from to , which can be used as array indices.
Focus Problem โ try your best to solve this problem before continuing!
View Internal SolutionExample 1
A good example of coordinate compression in action is in the solution of USACO
Rectangular Pasture. Again, we won't delve into the full solution but instead
discuss how coordinate compression is applied. Since the solution uses
2D-prefix sums (another Silver topic), it is helpful if
all point coordinates are coordinate-compressed to the range to so
they can be used as array indices. Without coordinate compression, creating a
large enough array would result in a Memory Limit Exceeded verdict.
Below you will find the solution to Rectangular Pasture, which uses coordinate compression at the beginning. Observe how a custom comparator is used to sort the points:
Warning!
Due to Python's constant factor, the below code will TLE on a couple test cases despite having the correct time complexity.
import sysinput = sys.stdin.readlinen = int(input().strip())points = [list(map(int, input().strip().split())) for _ in range(n)]points.sort(key=lambda i: i[1])for i in range(n):points[i][1] = i + 1
The solution to Rectangular Pasture directly replaces coordinates with their compressed values, and forgets the real values of the coordinates because they are unnecessary. However, there may be problems for which we need to also remember the original values of coordinates that we compress.
Example 2
Focus Problem โ try your best to solve this problem before continuing!
This problem will require prefix sums and coordinate compression. However, the
implementation of coordinate compression in this solution will also require
remembering values in addition to compressing them (as opposed to just replacing
the original values, as in the last problem). If you just want to focus on the
implementation of coordinate compression and how it can switch between
compressed indices and original values, see the contracted code below. indices
is a list of values that need to be compressed. After it gets sorted and has
duplicate values removed, it is ready to use. The method getCompressedIndex
takes in an original value, and binary searches for its
position in indices to get its corresponding compressed index. To go from a
compressed index to an original value, the code can just access that index in
indices.
We also provide a more detailed explanation:
Detailed Explanation
N, Q = map(int, input().split())coordinates = {-1} # dummy coordinate to the left of everythingupdates = dict()queries = []for n in range(N): # read updatesleft, right, value = map(int, input().split())coordinates.add(left)coordinates.add(right)
Problems
Pro Tip
Many of the problems below may use other Silver concepts, such as prefix sums.
| Status | Source | Problem Name | Difficulty | Tags | ||
|---|---|---|---|---|---|---|
| CSES | Easy | Show TagsPrefix Sums, Sorting | ||||
| CF | Normal | Show TagsCoordinate Compression, Prefix Sums, Sorting | ||||
| Silver | Normal | Show TagsPrefix Sums, Sorting | ||||
| Silver | Normal | Show TagsPrefix Sums, Sorting | ||||
| Silver | Normal | Show TagsSorting | ||||
| Silver | Normal | Show TagsSorting | ||||
| Gold | Normal | Show TagsPrefix Sums, Sorting | ||||
| CF | Normal | Show TagsSorting | ||||
| CF | Normal | Show TagsPrefix Sums, Sorting | ||||
| CF | Normal | Show TagsSorting | ||||
| Silver | Hard | Show TagsSorting | ||||
| Silver | Hard | Show TagsSorting | ||||
| Silver | Very Hard | Show TagsSorting | ||||
Quiz
What would the list be coordinate compressed to?
Module Progress:
Join the USACO Forum!
Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!