Как отсортировать массив: встроенная сортировка в Python, JavaScript и Java

Как отсортировать массив: встроенная сортировка в Python, JavaScript и Java Полезное

Сортировка массива — это перестановка его элементов в порядке возрастания или убывания по выбранному признаку: числу, строке, дате или полю объекта. На практике массив сортируют не самописным алгоритмом, а встроенной функцией языка: sorted() и list.sort() в Python, arr.sort() и arr.toSorted() в JavaScript, Arrays.sort() и List.sort() в Java. Они работают за O(n log n) в худшем случае и уже отлажены, поэтому писать свою сортировку в рабочем коде обычно незачем.

Ниже — как вызвать сортировку в каждом из трех языков, как задать порядок ключом или компаратором, что такое стабильность и почему [10, 9, 1].sort() в JavaScript дает «неправильный» ответ. Примеры проверены 24.09.2026 на Python 3.14.7, Node.js 24.21 и OpenJDK 25.0.4.

Четыре понятия, без которых сортировку не настроить

  • На месте или копия. Сортировка на месте меняет исходный массив (list.sort(), arr.sort(), Arrays.sort()); копирующая возвращает новый, исходный не трогает (sorted(), toSorted()).
  • Ключ — значение, которое сортировка вычисляет из элемента и сравнивает вместо него: длину строки, поле score, строку в нижнем регистре.
  • Компаратор — функция от двух элементов, которая отвечает «кто раньше»: отрицательное число — первый раньше, ноль — равны, положительное — второй раньше.
  • Стабильность — элементы с равными ключами сохраняют исходный взаимный порядок. Это важно, когда сортируют в несколько проходов или по одному полю из многих.

Шпаргалка по трем языкам

Задача Python JavaScript Java
На месте a.sort() a.sort(cmp) Arrays.sort(a), list.sort(cmp)
Новая копия sorted(a) a.toSorted(cmp) a.clone(), затем Arrays.sort
По убыванию reverse=True (x, y) => y - x Comparator.reverseOrder()
По признаку key=... компаратор Comparator.comparing(...)
Стабильна да, гарантировано да, со стандарта ES2019 объекты — да; примитивы — не важно
По умолчанию естественный порядок как строки естественный порядок

Последняя строка — главный источник ошибок, к ней вернемся в разделе про JavaScript.

Python: sorted, list.sort и параметр key

Минимальный полный пример: числа, убывание и сортировка записей по двум полям.

nums = [5, 2, 9, 1, 5, 6]
print(sorted(nums))                # новый список
print(nums)                        # исходный не изменился
nums.sort(reverse=True)            # сортировка на месте
print(nums)

students = [
    {"name": "Оля", "group": 2, "score": 90},
    {"name": "Иван", "group": 1, "score": 75},
    {"name": "Петр", "group": 2, "score": 75},
    {"name": "Анна", "group": 1, "score": 90},
]
# по баллам по убыванию, при равенстве - по имени по возрастанию
res = sorted(students, key=lambda s: (-s["score"], s["name"]))
print([s["name"] for s in res])
[1, 2, 5, 5, 6, 9]
[5, 2, 9, 1, 5, 6]
[9, 6, 5, 5, 2, 1]
['Анна', 'Оля', 'Иван', 'Петр']

Разбор по строкам. sorted() принимает любой итерируемый объект и возвращает новый список. list.sort() меняет список и возвращает None, поэтому nums = nums.sort() — частая ошибка: в переменной окажется None.

Ключ-кортеж (-score, name) сравнивается поэлементно: сначала баллы (минус превращает возрастание в убывание), при равенстве — имя. Минус работает только для чисел. Для строк по убыванию используют два стабильных прохода: сначала по второстепенному полю, потом по главному с reverse=True.

Функция key вызывается один раз на элемент, а не на каждое сравнение, поэтому дорогое вычисление в ключе обходится дешевле, чем в компараторе. Компаратор в стиле «сравни два» в Python 3 тоже доступен — через functools.cmp_to_key, когда правило не выражается ключом.

Стабильность на примере

rows = [("Иван", 1), ("Анна", 2), ("Петр", 1), ("Оля", 2)]
by_group = sorted(rows, key=lambda r: r[1])
print(by_group)
[('Иван', 1), ('Петр', 1), ('Анна', 2), ('Оля', 2)]

Внутри группы 1 Иван остался перед Петром, как во входе. Это гарантия языка, а не совпадение: на нее можно опираться.

Строки: регистр и буква «е» с точками

По умолчанию строки сравниваются по кодовым точкам Unicode. Заглавные кириллические буквы идут раньше строчных, а буква U+0451 («е» с двумя точками) стоит после всего алфавита «а-я»: слово «еж», написанное через нее, уходит в конец списка.

words = ["яблоко", "Банан", "ежевика", "ёж", "арбуз"]
print(sorted(words))
print(sorted(words, key=str.casefold))
print(sorted(words, key=lambda w: w.casefold().replace("ё", "е")))
['Банан', 'арбуз', 'ежевика', 'яблоко', 'ёж']
['арбуз', 'Банан', 'ежевика', 'яблоко', 'ёж']
['арбуз', 'Банан', 'ёж', 'ежевика', 'яблоко']

Замена U+0451 на обычную «е» в ключе — упрощение для русского текста. Полноценный порядок по правилам языка дает библиотека сопоставления с учетом локали (например, ICU); стандартный locale.strxfrm зависит от настроек ОС.

JavaScript: sort, toSorted и ловушка «сортировки как строк»

Без компаратора Array.prototype.sort приводит элементы к строкам и сравнивает их посимвольно. Для чисел это почти всегда не то, что нужно.

const nums = [10, 9, 1, 100, 25];
console.log(nums.sort());                 // ловушка
console.log(nums.sort((a, b) => a - b));  // числа по возрастанию

const src = [3, 1, 2];
const copy = src.toSorted((a, b) => b - a);
console.log(copy, src);

const users = [
  { name: "Оля", age: 30 },
  { name: "Иван", age: 25 },
  { name: "Петр", age: 30 },
];
users.sort((a, b) => b.age - a.age || a.name.localeCompare(b.name, "ru"));
console.log(users.map(u => u.name).join(", "));
[ 1, 10, 100, 25, 9 ]
[ 1, 9, 10, 25, 100 ]
[ 3, 2, 1 ] [ 3, 1, 2 ]
Оля, Петр, Иван

Первая строка вывода: «100» раньше «25», потому что символ «1» меньше «2». Исправление — компаратор (a, b) => a - b. Он корректен для обычных чисел, но не для NaN: с ним результат сравнения не определен, такие значения лучше отфильтровать заранее.

sort() меняет массив на месте и возвращает ссылку на него же. toSorted() (ES2023, есть в Node.js 20+ и актуальных браузерах) возвращает копию. В старых средах аналог — [...src].sort(cmp).

Сортировка по нескольким полям в JS строится через ||: если первая разница равна 0, берется вторая. Для строк — localeCompare с локалью, а не <: он учитывает регистр и алфавит языка.

const words = ["яблоко", "Банан", "ёж", "ежевика", "арбуз"];
console.log(words.toSorted());
console.log(words.toSorted((a, b) => a.localeCompare(b, "ru")));
[ 'Банан', 'арбуз', 'ежевика', 'яблоко', 'ёж' ]
[ 'арбуз', 'Банан', 'ёж', 'ежевика', 'яблоко' ]

Частая ошибка — компаратор, возвращающий true/false вместо числа: [3, 1, 2].sort((a, b) => a > b) в Node.js 24 вернул [ 3, 1, 2 ] без изменений. false превращается в 0 («равны»), и движок не получает сигнала «первый раньше». Результат такой ошибки зависит от движка и входа, поэтому компаратор всегда должен возвращать число.

Java: Arrays.sort и Comparator

import java.util.*;

public class SortDemo {
    record Student(String name, int group, int score) {}

    public static void main(String[] args) {
        int[] nums = {5, 2, 9, 1, 5, 6};
        Arrays.sort(nums);
        System.out.println(Arrays.toString(nums));

        Integer[] boxed = {5, 2, 9, 1, 5, 6};
        Arrays.sort(boxed, Comparator.reverseOrder());
        System.out.println(Arrays.toString(boxed));

        List<Student> list = new ArrayList<>(List.of(
            new Student("Оля", 2, 90), new Student("Иван", 1, 75),
            new Student("Петр", 2, 75), new Student("Анна", 1, 90)));
        list.sort(Comparator.comparingInt(Student::score).reversed()
                            .thenComparing(Student::name));
        list.forEach(s -> System.out.print(s.name() + " "));
        System.out.println();

        Integer[] big = {-2_000_000_000, 2_000_000_000, 0};
        Arrays.sort(big, (a, b) -> a - b);
        System.out.println("a - b:           " + Arrays.toString(big));
        Arrays.sort(big, Integer::compare);
        System.out.println("Integer.compare: " + Arrays.toString(big));
    }
}
[1, 2, 5, 5, 6, 9]
[9, 6, 5, 5, 2, 1]
Анна Оля Иван Петр 
a - b:           [0, 2000000000, -2000000000]
Integer.compare: [-2000000000, 0, 2000000000]

Файл запускается одной командой java SortDemo.java (Java 11+; record — с Java 16). Что важно:

  • Arrays.sort(int[]) сортирует только по возрастанию: компаратор к массиву примитивов не передать. Для убывания берут Integer[] с Comparator.reverseOrder() или сортируют и разворачивают массив.
  • Для примитивов используется Dual-Pivot Quicksort, он нестабилен, но у равных чисел нечего различать. Для объектов (Arrays.sort(Object[]), List.sort) используется стабильная сортировка на основе TimSort, это записано в документации.
  • Компаратор (a, b) -> a - b ломается на больших числах: разность 2 000 000 000 - (-2 000 000 000) переполняет int, меняет знак, и массив получается неотсортированным. Правильно — Integer.compare или Comparator.comparingInt.

Сложность и память: что обещают встроенные сортировки

Реализация Время, худший случай Уже отсортированный вход Доп. память Стабильна
Python sort/sorted O(n log n) O(n) до ~n/2 ссылок да
JS sort в V8 (TimSort) O(n log n) O(n) до ~n/2 да
Java Arrays.sort(int[]) O(n log n) зависит от входа O(log n) и выше не важно
Java объекты, List.sort O(n log n) O(n) до ~n/2 да

O(n log n) — нижняя граница для любой сортировки, которая только сравнивает элементы: быстрее в худшем случае такие алгоритмы не бывают. Реальное время еще зависит от стоимости сравнения: компаратор с localeCompare или ключ с тяжелым вычислением замедляет сортировку сильнее, чем сам алгоритм.

Как устроены пузырек, вставки, быстрая сортировка и слияние изнутри, с прогоном по шагам — в статье Сортировка: основные принципы и сравнение алгоритмов.

Когда встроенной сортировки мало

  • Нужны только k лучших из большого массива — не сортируйте все: heapq.nlargest(3, prices) в Python работает за O(n log k).
  • Целые числа в малом диапазоне (оценки 1-5, возраст 0-120) — сортировка подсчетом дает O(n + m) без сравнений, где m — число возможных значений.
  • Данные не помещаются в память — внешняя сортировка слиянием по частям или ORDER BY в базе данных, где есть индекс.
  • Учебная задача или собеседование — просят реализовать алгоритм руками; в рабочем коде это исключение.
import heapq
prices = [420, 15, 999, 73, 250, 610, 8]
print(heapq.nlargest(3, prices))
[999, 610, 420]

Если не получилось: симптом -> причина -> что делать

Симптом Причина Исправление
JS: [1, 10, 100, 25, 9] нет компаратора, сравнение как строк (a, b) => a - b
Python: в переменной None присвоили результат list.sort() sorted() или вызов без присваивания
Python: TypeError: '<' not supported between instances of 'str' and 'int' в списке смешаны типы привести к одному типу или задать key
Java: порядок «прыгает» на больших числах переполнение в a - b Integer.compare
Заглавные раньше строчных, «е» с точками в конце сравнение по кодовым точкам casefold плюс замена «е» с точками в ключе (одного casefold мало) или localeCompare(..., "ru")
JS: массив не изменился компаратор вернул true/false возвращать число

Выводы

  • Для сортировки массива в рабочем коде берите встроенную функцию: она работает за O(n log n), а в Python, JavaScript и для объектов в Java еще и стабильна.
  • Различайте сортировку на месте (sort) и копию (sorted, toSorted); list.sort() возвращает None.
  • Порядок задают ключом (Python, Comparator.comparing в Java) или компаратором, который возвращает число, а не булево значение.
  • В JavaScript без компаратора числа сортируются как строки; в Java a - b в компараторе переполняется — используйте Integer.compare.
  • Строки на русском сортируйте без учета регистра (ключ casefold) и с правильным местом «е» с точками (замена в ключе или localeCompare с локалью ru).

Где применяется / связь с практикой

Освойте тему на практике

Сортировка — часть почти любой задачи с данными: ранжирование выдачи, топ-N товаров, группировка записей перед слиянием, подготовка к бинарному поиску. На собеседованиях ее проверяют с двух сторон: умеет ли кандидат правильно вызвать встроенную функцию с ключом и почему она работает за O(n log n). Разобрать сортировки, кучи и оценку сложности на задачах можно на курсе «Алгоритмы и структуры данных», а посмотреть формат занятий — на открытых уроках Otus.

FAQ

Можно ли отсортировать словарь в Python?
Сам словарь хранит порядок вставки, но метода sort у него нет. Берут sorted(d.items(), key=lambda kv: kv[1]) и при необходимости собирают новый словарь через dict(...).

Как перемешать массив, а не отсортировать?
Сортировка со случайным компаратором дает неравномерное перемешивание. Правильно — random.shuffle в Python, Collections.shuffle в Java или алгоритм Фишера-Йетса в JavaScript.

Нужно ли сортировать массив перед поиском элемента?
Только если поисков будет много: сортировка стоит O(n log n), зато каждый бинарный поиск потом — O(log n). Для одного поиска достаточно линейного прохода за O(n).

OTUS Журнал
Бесплатные открытые уроки (поп-ап)