Start learning free

LESSON 225 OF 414 · SORTING

Sorting in Rust: sort, sort_by and sort_by_key

Lesson Read the full explanation

In order to sort a Vec we can use the .sort() method, which sorts the elements of a given Vec in ascending order (default), descending order or user-defined order.

Syntax:

The syntax of the sorting methods is:

vec.sort();                           // ascending order (default)
vec.sort_by(|a, b| a.cmp(b));        // with custom comparator
vec.sort_by_key(|k| /* key expr */); // sort by a key

Let's see some examples below. Sorting a Vec of integers in ascending order

let mut numbers = vec![11, 3, 7, 5, 2];

numbers.sort(); // numbers becomes [2, 3, 5, 7, 11]

Sorting a Vec of integers in descending order

let mut numbers = vec![11, 3, 7, 5, 2];

numbers.sort_by(|a, b| b.cmp(a));

Sorting a Vec of Strings in descending order

let mut names = vec!["Mircea", "Andy", "Xavier", "Michael", "Christina"];

names.sort_by(|a, b| b.cmp(a));  // --> ["Xavier", "Mircea", "Michael", "Christina", "Andy"]

Sort with custom function (comparator):

sort_by() supports a basic comparison function that we can use to specify how we want to sort:

fn compare(a: &i32, b: &i32) -> std::cmp::Ordering {
    a.cmp(b)
}

This can be written as a lambda function (nameless function we can easily pass to sort_by()) as follows:

|a: &i32, b: &i32| -> std::cmp::Ordering {
    a.cmp(b)
}

We can make it even shorter by dropping the type annotations since sort_by() will infer the types. We use .cmp() which directly returns std::cmp::Ordering

|a, b| a.cmp(b)

When the sort_by() function compares two values, it sends the values to the compare function, and sorts the values according to the returned value:

  • If the result is Ordering::Less then a is sorted before b.
  • If the result is Ordering::Greater then b is sorted before a.

All we need to is using the compare function (as a lambda function) inside the sort_by() function:

let mut numbers = vec![3, 23, 12];

// using the short version of the lambda function
numbers.sort_by(|a, b| a.cmp(b)); // --> 3, 12, 23

To demonstrate a basic example, let's assume the requirement for ordering a specific list is the length of the strings in the list, shortest to longest. We will be using the len() method of each string to compare them.

let mut words = vec!["banana", "pie", "Washington", "book"];

words.sort_by_key(|a| a.len()); // --> ["pie", "book", "banana", "Washington"]

Example: sorting array of pairs

Let's say we want to sort an array, where each element is another array consisting of two numbers.

let mut pairs = vec![vec![1, 5], vec![12, 5], vec![10, 1], vec![1, 3]];

pairs.sort_by(|a, b| {
    // in case the first values of each pair are equal,
    // compare them using their second values
    if a[0] == b[0] {
        a[1].cmp(&b[1])
    } else {
        a[0].cmp(&b[0])
    }
});
// pairs --> [[1, 3], [1, 5], [10, 1], [12, 5]]

Time Complexity:

Sorting a Vec via .sort() takes O(n * log n * C) time, where C is the time taken to compare two elements from the Vec.

It takes O(1) (constant time) to compare two numbers, therefore sorting a Vec of numbers takes O(n log n).

However, it takes O(L) to compare two strings, therefore sorting a Vec of strings takes O(n * log n * L), where L is the average length of the strings.


Space Complexity:

The sort() method sorts the elements of a Vec in place (it changes the Vec directly and returns nothing), but it is a stable sort that may allocate a temporary buffer of up to the size of the Vec, so the extra space used is O(n). sort_unstable() needs no such buffer.


Assignment
Follow the Coding Tutorial and let's sort some Vecs.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 6
    Ctrl + Enter to check

    RUST · SORTING

    Sorting in Rust, explained

    What sorting a Vec means

    Rust sorts a Vec in place: the methods change the vector they are called on and return nothing (the unit value ()). So the vector must be declared mut, and writing let sorted = v.sort(); gives you (), not a sorted vector.

    There are three everyday methods:

    vec.sort();                           // ascending order (default)
    vec.sort_by(|a, b| a.cmp(b));        // with a comparator
    vec.sort_by_key(|k| /* key expr */); // sort by a key
    

    They are stable: elements that compare equal keep their original relative order. Each has an unstable twin (sort_unstable, sort_unstable_by, sort_unstable_by_key) that doesn't promise this. For plain integers it makes no visible difference, because equal integers are indistinguishable.

    Default order: sort()

    sort() uses the type's natural order. It works on any element type that implements Ord, which includes integers, char, &str, String, tuples and vectors of those.

    let mut numbers = vec![11, 3, 7, 5, 2];
    numbers.sort(); // numbers becomes [2, 3, 5, 7, 11]
    

    Tuples and vectors compare element by element, from the left. That makes sort() enough for a vector of pairs when "first value, then second value" is the order you want:

    let mut pairs = vec![vec![1, 5], vec![12, 5], vec![10, 1], vec![1, 3]];
    pairs.sort();
    // pairs is now [[1, 3], [1, 5], [10, 1], [12, 5]]
    

    Custom order: sort_by with a comparator

    sort_by takes a closure that receives two references, a and b, and returns a std::cmp::Ordering: Less, Equal or Greater. If it returns Less, a goes before b; if Greater, b goes before a. The method cmp produces such a value, so the ascending order is |a, b| a.cmp(b). Swapping the operands reverses it:

    let mut numbers = vec![11, 3, 7, 5, 2];
    numbers.sort_by(|a, b| b.cmp(a));
    // numbers is now [11, 7, 5, 3, 2]
    

    The long form of the same closure spells out the types: |a: &i32, b: &i32| -> std::cmp::Ordering { a.cmp(b) }. The short form works because the compiler infers them.

    Chaining comparisons

    To sort by one field and break ties with another, chain comparisons with then. The second comparison is used only when the first returns Equal:

    let mut coordinates = vec![(3, 2), (2, 3), (3, 5)];
    coordinates.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
    // coordinates is now [(2, 3), (3, 5), (3, 2)]
    

    The first fields ascend (a.0.cmp(&b.0)), and among equal first fields the second ones descend (b.1.cmp(&a.1), operands swapped). Tuple fields are read with .0 and .1, and cmp takes the other value by reference, hence the &.

    Sorting by a key: sort_by_key

    When the order is "compare these computed values", sort_by_key is shorter. The closure returns a key for each element, and the vector is ordered by the keys:

    let mut words = vec!["banana", "pie", "Washington", "book"];
    words.sort_by_key(|a| a.len());
    // words is now ["pie", "book", "banana", "Washington"]
    

    The vector keeps its original elements, only in the order of their keys. The key type must itself implement Ord. A name sorted ignoring case uses a lowercase copy as the key:

    let mut names = vec!["Mircea", "john", "Andy"];
    names.sort_by_key(|name| name.to_lowercase());
    // names is now ["Andy", "john", "Mircea"]
    

    Without the key, names.sort() compares bytes, and every uppercase letter comes before every lowercase one, giving ["Andy", "Mircea", "john"]. Note that sort_by_key calls the closure on every comparison, so it builds a new lowercase string many times. sort_by_cached_key computes each key once, which helps when keys are expensive to build.

    For a descending order by key, wrap the key in std::cmp::Reverse: d.sort_by_key(|&x| std::cmp::Reverse(x)) turns [3, 1, 2] into [3, 2, 1].

    Gotchas

    • Floats can't use sort(). f64 doesn't implement Ord, because NaN isn't comparable. Calling sort() on a Vec<f64> is a compile error: the trait bound {float}: Ord is not satisfied. Use a comparator: v.sort_by(|a, b| a.partial_cmp(b).unwrap()) works when no value is NaN, and total_cmp handles every value.
    • Forgetting mut. Calling v.sort() on a non-mut binding fails with cannot borrow `v` as mutable, as it is not declared as mutable.
    • Using the result. Sorting returns (), so the sorted vector is the variable you called it on.
    • A reversed comparator. a.cmp(b) is ascending and b.cmp(a) is descending. Mixing them up flips the whole order.

    Complexity

    Sorting a Vec takes O(n log n * C) time, where C is the cost of one comparison. Comparing two integers is O(1), so sorting numbers is O(n log n). Comparing two strings costs O(L), where L is their length, so sorting strings takes O(n log n * L).

    The vector is rearranged in place and nothing new is returned, but the stable methods may use a temporary buffer, so their extra space can reach O(n). The sort_unstable family works fully in place and needs only O(1) extra space apart from a small amount of stack for the recursion. That difference matters when a problem insists on O(1) space.

    The exercise

    The lesson asks for three small sorts. Each is built the same way: a mut variable, then one sorting call.

    1. Numbers, descending. Declare numbers as vec![2, 1, 3, 10] and call sort_by with |a, b| b.cmp(a). The result is [10, 3, 2, 1].
    2. Coordinates. Declare coordinates as vec![(3, 2), (2, 3), (3, 5)]. Sort by the first value ascending and break ties by the second value descending, with a.0.cmp(&b.0).then(b.1.cmp(&a.1)). The result is [(2, 3), (3, 5), (3, 2)].
    3. Names, ignoring case. Declare names as vec!["Mircea", "john", "Andy"], a vector of &str, and call sort_by_key with |name| name.to_lowercase(). The result is ["Andy", "john", "Mircea"].
    let mut numbers = vec![2, 1, 3, 10];
    numbers.sort_by(|a, b| b.cmp(a));
    
    let mut coordinates = vec![(3, 2), (2, 3), (3, 5)];
    coordinates.sort_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
    
    let mut names = vec!["Mircea", "john", "Andy"];
    names.sort_by_key(|name| name.to_lowercase());
    

    Key takeaways

    • sort(), sort_by() and sort_by_key() sort a mut vector in place and return ().
    • A comparator returns an Ordering. a.cmp(b) sorts ascending, b.cmp(a) descending, and then breaks ties.
    • sort_by_key orders by a computed key, and Reverse inverts it.
    • Floats need sort_by with partial_cmp or total_cmp.
    • Sorting numbers takes O(n log n) time. Use sort_unstable when you need the in-place guarantee.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free