Как отсортировать вектор в Rust: от новичка до профи
Представьте: вы пишете на Rust программу, которая собирает список пользователей — имена, возраст, рейтинг. Данные приходят в хаотичном порядке. Вам нужно вывести их по алфавиту или, скажем, отсортировать по возрасту. Знакомая задача? Если вы заглядывали на Stack Overflow, то наверняка видели вопрос «How to sort a vector in Rust?» — он собрал тысячи просмотров. И это не случайно.
Сортировка в Rust — одна из тех тем, где «просто вызвать sort()» может не сработать, если вы не знаете пары нюансов. В этой статье мы разберем все популярные способы сортировки вектора: от базового метода sort() до кастомных компараторов и выбора между стабильной и нестабильной сортировкой. Вы узнаете, как отсортировать вектор в Rust правильно, быстро и без паники.
Базовый метод: sort() — когда всё просто
Самый простой способ отсортировать вектор — использовать метод sort(). Он работает для типов, которые реализуют трейт Ord — а это все стандартные примитивы (числа, строки, символы).
fn main() {
let mut numbers = vec![4, 2, 9, 1, 5, 6];
numbers.sort();
println!("{:?}", numbers); // Вывод: [1, 2, 4, 5, 6, 9]
}
Обратите внимание: переменная numbers объявлена как mut (mutable). Метод sort() изменяет вектор на месте, поэтому без mut код не скомпилируется. Это типичная ошибка новичков — забыть про изменяемость.
То же самое работает для строк:
fn main() {
let mut fruits = vec!["banana", "apple", "cherry", "date"];
fruits.sort();
println!("{:?}", fruits); // Вывод: ["apple", "banana", "cherry", "date"]
}
Сортировка строк по умолчанию — лексикографическая (по алфавиту). Если вам нужна сортировка по длине или игнорируя регистр — читайте раздел про кастомные компараторы.
sort_unstable(): когда скорость важнее порядка
В Rust есть два варианта сортировки: sort() (стабильная) и sort_unstable() (нестабильная). Разница в том, что стабильная сортировка сохраняет относительный порядок равных элементов, а нестабильная — нет. Звучит академично, но на практике это важно, если вы сортируете сложные структуры и хотите сохранить порядок вставки для равных ключей.
Однако у sort_unstable() есть преимущество: она работает быстрее и использует меньше памяти. Для больших векторов (миллионы элементов) разница может быть существенной.
fn main() {
let mut scores = vec![85, 92, 85, 70, 92, 60];
scores.sort_unstable();
println!("{:?}", scores); // Вывод: [60, 70, 85, 85, 92, 92]
// Порядок двух 85 и двух 92 может отличаться от исходного
}
Когда выбирать sort_unstable? Если вам не важен порядок равных элементов и вы гонитесь за производительностью. Если же важна предсказуемость (например, сортировка таблицы с несколькими колонками), используйте sort().
sort_by(): кастомные компараторы
Что делать, если нужно отсортировать вектор по нестандартному правилу? Например, числа по убыванию, строки по длине, или пользовательские структуры по одному из полей. Для этого существует метод sort_by(), который принимает замыкание-компаратор.
Компаратор должен возвращать Ordering — один из вариантов: Less, Equal или Greater. Самый простой способ получить такой результат — использовать методы .cmp() или .partial_cmp().
Пример: сортировка чисел по убыванию.
fn main() {
let mut numbers = vec![4, 2, 9, 1, 5];
numbers.sort_by(|a, b| b.cmp(a));
println!("{:?}", numbers); // Вывод: [9, 5, 4, 2, 1]
}
Обратите внимание: мы меняем местами a и b — b.cmp(a) даёт обратный порядок. Можно также использовать a.cmp(b).reverse().
Сортировка строк по длине:
fn main() {
let mut words = vec!["rust", "python", "c", "java", "javascript"];
words.sort_by(|a, b| a.len().cmp(&b.len()));
println!("{:?}", words); // Вывод: ["c", "rust", "java", "python", "javascript"]
}
А вот пример с игнорированием регистра:
fn main() {
let mut words = vec!["Apple", "banana", "Cherry", "date"];
words.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
println!("{:?}", words); // Вывод: ["Apple", "banana", "Cherry", "date"]
}
Сортировка пользовательских структур
Чаще всего вам придётся сортировать не простые числа, а структуры данных. Допустим, у нас есть список пользователей, и мы хотим отсортировать их по возрасту.
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
fn main() {
let mut users = vec![
User { name: "Alice".to_string(), age: 30 },
User { name: "Bob".to_string(), age: 25 },
User { name: "Charlie".to_string(), age: 35 },
];
// Сортировка по возрасту (по возрастанию)
users.sort_by(|a, b| a.age.cmp(&b.age));
println!("{:?}", users);
// Вывод: [User { name: "Bob", age: 25 }, User { name: "Alice", age: 30 }, User { name: "Charlie", age: 35 }]
}
Если вам нужно сортировать по нескольким полям (сначала по возрасту, потом по имени), можно сцепить вызовы cmp:
users.sort_by(|a, b| {
a.age.cmp(&b.age) // сначала по возрасту
.then(a.name.cmp(&b.name)) // потом по имени
});
Метод then() работает как цепочка: если первый cmp вернул Equal, то вызывается второй. Очень удобно для многоключевой сортировки.
sort_by_key() и sort_unstable_by_key()
Если ваш компаратор просто извлекает ключ из элемента, можно использовать более лаконичные методы sort_by_key() и sort_unstable_by_key(). Они принимают замыкание, которое возвращает ключ, реализующий Ord.
Пример: сортировка пользователей по возрасту с помощью sort_by_key.
users.sort_by_key(|u| u.age);
Это эквивалентно предыдущему примеру с