Avatar ·

Как отсортировать вектор в Rust: по возрастанию, по убыванию и с кастомной логикой

Привет! Я только начал изучать Rust и столкнулся с проблемой: не могу понять, как правильно сортировать вектор. Я привык к Python, где можно просто вызвать `sorted()`, а тут всё иначе. Допустим, у меня есть вектор чисел: `let mut numbers = vec![5, 2, 8, 1, 9];`. Я хочу отсортировать его по возрастанию, чтобы получить `[1, 2, 5, 8, 9]`. Потом, возможно, понадобится отсортировать по убыванию. Я пробовал писать что-то вроде `numbers.sort()` и это вроде работает, но я не уверен, что это правильно. Ещё у меня есть вектор строк: `let mut words = vec!["banana", "apple", "cherry", "date"];`. Как отсортировать его в алфавитном порядке? И что, если я хочу отсортировать по длине строки (например, от самой короткой к самой длинной)? И последнее: у меня есть структура `Person` с полями `name` и `age`. Я создал вектор `Vec`. Как отсортировать его сначала по возрасту, а потом по имени? В Python я бы использовал `lambda` или `itemgetter`, а в Rust что? Пожалуйста, объясните на примерах, как работают методы `sort`, `sort_by`, `sort_by_key` и в чём между ними разница. И ещё: что такое `Ord` и `PartialOrd`? Я видел эти трейты в ошибках компилятора, но не понимаю, зачем они нужны. Заранее спасибо! <<>>

Основы сортировки в Rust

В Rust сортировка векторов реализована через методы трейта Slice. Все основные методы сортируют на месте (in-place), изменяя исходный вектор. Если нужна новая отсортированная копия, можно сначала клонировать вектор.

1. Простая сортировка по возрастанию: sort()

Метод sort() работает для типов, которые реализуют трейт Ord (полная упорядоченность). Числа, строки и многие стандартные типы его реализуют.

fn main() {
    let mut numbers = vec![5, 2, 8, 1, 9];
    numbers.sort();
    println!("{:?}", numbers); // [1, 2, 5, 8, 9]

    let mut words = vec!["banana", "apple", "cherry", "date"];
    words.sort();
    println!("{:?}", words); // ["apple", "banana", "cherry", "date"]
}

Важно: sort() использует стабильную сортировку (алгоритм merge sort), сохраняя относительный порядок равных элементов.

2. Сортировка по убыванию: sort_by() с reverse()

Самый простой способ — отсортировать по возрастанию, а затем развернуть вектор:

let mut numbers = vec![5, 2, 8, 1, 9];
numbers.sort();
numbers.reverse();
println!("{:?}", numbers); // [9, 8, 5, 2, 1]

Или использовать sort_by() с кастомной функцией сравнения:

numbers.sort_by(|a, b| b.cmp(a));
println!("{:?}", numbers); // [9, 8, 5, 2, 1]

Здесь cmp() возвращает Ordering::Less, Ordering::Equal или Ordering::Greater. Меняя аргументы местами, мы инвертируем порядок.

3. Сортировка по ключу: sort_by_key()

Этот метод принимает замыкание, которое извлекает ключ из каждого элемента. Идеально подходит для сортировки строк по длине:

let mut words = vec!["banana", "apple", "cherry", "date"];
words.sort_by_key(|word| word.len());
println!("{:?}", words); // ["date", "apple", "banana", "cherry"]
// date - 4, apple - 5, banana - 6, cherry - 6

Обратите внимание: если ключи равны (как "banana" и "cherry"), порядок сохраняется (стабильная сортировка).

4. Сортировка структур: sort_by() для сложной логики

Для структур нужно реализовать трейт Ord или использовать sort_by(). Рассмотрим пример с Person:

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let mut people = vec![
        Person { name: "Alice".to_string(), age: 30 },
        Person { name: "Bob".to_string(), age: 25 },
        Person { name: "Charlie".to_string(), age: 30 },
        Person { name: "David".to_string(), age: 20 },
    ];

    // Сортировка по возрасту (по возрастанию)
    people.sort_by(|a, b| a.age.cmp(&b.age));
    println!("По возрасту: {:?}", people);

    // Сортировка по возрасту (по убыванию), затем по имени
    people.sort_by(|a, b| {
        // Сначала сравниваем возраст (по убыванию)
        let age_cmp = b.age.cmp(&a.age);
        // Если возраст одинаковый — сравниваем имя
        if age_cmp == std::cmp::Ordering::Equal {
            a.name.cmp(&b.name)
        } else {
            age_cmp
        }
    });
    println!("По возрасту (убыв.) и имени: {:?}", people);
}

5. Трейты Ord и PartialOrd

PartialOrd — частичный порядок. Позволяет сравнивать элементы, но не гарантирует, что любые два элемента можно сравнить (например, числа с плавающей точкой f64: NaN не сравнимо с другими числами).

Ord — полный порядок. Все элементы можно сравнить. Требует реализации PartialOrd, Eq и PartialEq.

Большинство стандартных типов (целые числа, строки, кортежи) реализуют Ord. Для своих структур можно вывести Ord через #[derive(Ord, PartialOrd, Eq, PartialEq)], но порядок будет лексикографическим по полям.

6. Альтернатива: sort_unstable()

Если порядок равных элементов не важен, используйте sort_unstable() — он быстрее (алгоритм quicksort) и потребляет меньше памяти:

let mut numbers = vec![5, 2, 8, 1, 9];
numbers.sort_unstable();
println!("{:?}", numbers); // [1, 2, 5, 8, 9]

Для sort_unstable_by_key() и sort_unstable_by() есть аналоги.

Итог

  • sort() — для простой сортировки по возрастанию (стабильная).
  • sort_by() — для кастомной логики сравнения.
  • sort_by_key() — для сортировки по вычисляемому ключу.
  • Для обратного порядка меняйте аргументы местами или используйте reverse().
  • Для структур реализуйте Ord или используйте sort_by().
<<>>

Сортировка с помощью итераторов: sorted_by() из itertools

Если вы не хотите изменять исходный вектор, а получить новый отсортированный, можно использовать крейт itertools. Метод sorted_by() возвращает итератор, который можно собрать в новый вектор:

use itertools::Itertools;

fn main() {
    let numbers = vec![5, 2, 8, 1, 9];
    let sorted: Vec<i32> = numbers.iter()
        .sorted_by(|a, b| a.cmp(b))
        .cloned()
        .collect();
    println!("{:?}", sorted); // [1, 2, 5, 8, 9]
    println!("{:?}", numbers); // [5, 2, 8, 1, 9] — исходный не изменился
}

Также есть sorted_by_key() для сортировки по ключу:

let words = vec!["banana", "apple", "cherry", "date"];
let sorted_by_len: Vec<&str> = words.iter()
    .sorted_by_key(|word| word.len())
    .cloned()
    .collect();
println!("{:?}", sorted_by_len); // ["date", "apple", "banana", "cherry"]

Этот подход удобен, когда нужно сохранить оригинальный вектор или цепочка операций с итераторами уже используется в коде. Для добавления itertools в проект добавьте в