Перейти к основному содержимому

Упорядочивание коллекции

Сортировка означает расстановку элементов коллекции в определённом порядке.

У некоторых типов есть естественный порядок. Числа упорядочены по значению: 1 идёт перед 2. Строки упорядочены по алфавиту: "four" идёт перед "one". Для всего остального или когда вам нужен другой порядок, вы задаёте собственный порядок: функцию сравнения, которая смотрит на два элемента и говорит, какой из них идёт первым.

У массивов есть один метод сортировки, sort(), и две вещи о нём легко упустить:

  • sort() меняет массив на месте и возвращает тот же самый массив, поэтому исходный порядок теряется. Чтобы сохранить его, сортируйте копию: [...words].sort().
  • Без функции сравнения sort() сравнивает строковую форму значений. Для слов это нормально, но [10, 9, 2] получается как [ 10, 2, 9 ], потому что текст "10" идёт перед "2". Для чисел всегда передавайте функцию сравнения.

Функция сравнения получает два элемента, a и b, и возвращает число: отрицательное, когда первым идёт a, положительное, когда первым идёт b, и 0, когда они равны. a - b делает это для чисел, а a.localeCompare(b) — для строк.

sort() устойчива: элементы, которые функция сравнения считает равными, сохраняют свой исходный порядок.

В более новых версиях Node.js есть также toSorted() и toReversed(), которые возвращают новый массив и оставляют оригинал нетронутым. В примерах ниже массив копируется вручную через [...words], что работает везде.

В примерах ниже используются слова one, two, three, four.


Естественный порядок

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

[...words].sort() даёт новый массив в алфавитном порядке. Для убывающего порядка отдельного метода нет: сначала отсортируйте, затем переверните массив с помощью reverse(). Для чисел функция сравнения (a, b) => b - a делает это за один шаг.

У ваших собственных типов объектов естественного порядка нет. Вы задаёте его, написав функцию сравнения, которая возвращает отрицательное число, ноль или положительное число — то же правило, что и для любой другой функции сравнения. Затем вы можете передать её в sort() или использовать напрямую, чтобы сравнить два значения.

Отсортированная копия

[...words].sort() сортирует копию, поэтому сам words выводится после этого без изменений. Последние две строки показывают, зачем числам нужна функция сравнения: без неё [10, 9, 2] становится [ 10, 2, 9 ]; с (a, b) => a - b получается [ 2, 9, 10 ].

Prompt
Напишите программу на TypeScript, которая берёт слова one, two, three, four, сортирует их копию в естественном (алфавитном) порядке с помощью [...words].sort() и выводит отсортированную копию, а затем неизменённый оригинал. Затем выведите [10, 9, 2], отсортированный без функции сравнения и отсортированный с (a, b) => a - b, чтобы показать, зачем числам нужна такая функция.
Вывод: программа на TypeScript и захваченный вывод
Sorted copy

Сортировка по убыванию

Копия сортируется, затем reverse() переворачивает её, поэтому слова идут от "two" вниз до "four". Числа показывают более короткий путь для значений, которые можно вычитать: (a, b) => b - a сортирует [10, 9, 2] сразу в [ 10, 9, 2 ].

Prompt
Напишите программу на TypeScript, которая берёт слова one, two, three, four, сортирует их копию и переворачивает её с помощью reverse(), так что слова оказываются в убывающем алфавитном порядке, и выводит результат. Затем выведите [10, 9, 2], отсортированный функцией сравнения (a, b) => b - a.
Вывод: программа на TypeScript и захваченный вывод
Sorted descending

Тип со своим собственным порядком

У Version есть числа major и minor. compareVersion() сравнивает сначала major; только когда этот результат равен 0, решает minor, потому что || берёт первый результат, который не равен 0. Проверка compareVersion(...) > 0 отвечает на вопрос «больше ли первая версия?».

Prompt
Напишите программу на TypeScript с типом Version, у которого есть числа major и minor, и функцией compareVersion, которая возвращает отрицательное число, ноль или положительное число, сравнивая сначала major, а потом minor с помощью ||. Выведите, больше ли версия 1.2, чем 1.3, а затем — больше ли 2.0, чем 1.5, используя compareVersion(...) > 0.
Вывод: программа на TypeScript и захваченный вывод
A type with its own order

Собственные порядки

Собственный порядок — это любой порядок, который вы описываете функцией сравнения.

sort() принимает эту функцию как свой единственный аргумент. Поскольку sort() устойчива, элементы, которые функция считает равными, остаются в своём исходном порядке — именно это вам и нужно при сортировке слов по длине.

Частое желание — отсортировать по одному свойству каждого элемента. Специального метода для этого нет: напишите функцию сравнения, которая сравнивает это свойство, с - для чисел или localeCompare() для строк.

Для убывающего порядка поменяйте местами a и b в сравнении.

Чтобы отсортировать по одному признаку, а затем по другому, соедините два сравнения с помощью ||. Он берёт первый результат, который не равен 0, поэтому, когда первое сравнение считает элементы равными, спор решает второе.

Сортировка с функцией сравнения

Функция сравнения возвращает a.length - b.length. Отрицательный результат означает, что a короче и идёт первым, поэтому слова в итоге идут от самого короткого к самому длинному: [ 'c', 'bb', 'aaa' ].

Prompt
Напишите программу на TypeScript, которая берёт слова aaa, bb, c и сортирует их копию функцией сравнения (a, b) => a.length - b.length, так что короткие слова идут первыми. Выведите результат.
Вывод: программа на TypeScript и захваченный вывод
Sort with a compare function

Сортировка по длине

Слова сравниваются с помощью a.length - b.length. "one" и "two" оба длиной 3 буквы; поскольку sort() устойчива, "one" остаётся перед "two".

Prompt
Напишите программу на TypeScript, которая берёт слова one, two, three, four и сортирует их копию по длине с помощью (a, b) => a.length - b.length. Выведите результат и позвольте устойчивой сортировке сохранить исходный порядок слов одинаковой длины.
Вывод: программа на TypeScript и захваченный вывод
Sort by length

Сортировка по последней букве, по убыванию

a[a.length - 1] — это последняя буква слова. Буквы b и a сравниваются в обратном порядке, поэтому слово с самой большой последней буквой идёт первым: "four" (r), затем "two" (o), затем два слова, оканчивающиеся на «e», в их исходном порядке.

Prompt
Напишите программу на TypeScript, которая берёт слова one, two, three, four и сортирует их копию по последней букве в убывающем порядке, сравнивая последнюю букву b с последней буквой a с помощью localeCompare. Выведите результат.
Вывод: программа на TypeScript и захваченный вывод
Sort by last letter, descending

Сортировка по длине, затем по алфавиту

|| сначала сравнивает длины. Когда два слова одинаково длинные, он переходит к localeCompare(), который упорядочивает их по алфавиту. Результат выводится через join(' '), в одну строку: a b c aa bb aaa ccc.

Prompt
Напишите программу на TypeScript, которая берёт слова aaa, bb, c, b, a, aa, ccc и сортирует их копию сначала по длине, а потом по алфавиту, используя одну функцию сравнения, которая соединяет a.length - b.length и a.localeCompare(b) с помощью ||. Выведите результат в одну строку с помощью join(' ').
Вывод: программа на TypeScript и захваченный вывод
Sort by length, then alphabetically

Проверка отсортированного порядка

Иногда вы хотите лишь узнать, отсортирован ли массив уже, не сортируя его.

Встроенной проверки нет, но every() пишет её в одну строку: каждый элемент должен идти после предыдущего элемента. Небольшой помощник isSorted() ниже принимает функцию сравнения, поэтому один и тот же помощник может проверять возрастающий порядок, убывающий порядок или порядок по одному свойству. Для убывающего порядка вы передаёте функцию сравнения с поменянными местами a и b.

Проверка возрастания и убывания

isSorted() подтверждает, что 1, 2, 3, 4 возрастают. Тот же помощник с (a, b) => b - a подтверждает, что 4, 3, 2, 1 убывают. Элемент с индексом 0 принимается всегда, потому что перед ним нет элемента.

Prompt
Напишите программу на TypeScript с обобщённым помощником isSorted, который с помощью every() и функции сравнения проверяет, что каждый элемент идёт после предыдущего. Выведите, возрастают ли 1, 2, 3, 4, и убывают ли 4, 3, 2, 1, передав функцию сравнения с поменянными местами a и b.
Вывод: программа на TypeScript и захваченный вывод
Ascending and descending check

Проверка порядка по полю

Пользователи — это Alice (24), Bob (31) и Charlie (29). Функция сравнения смотрит только на age. Bob старше, чем Charlie, поэтому массив не отсортирован по возрасту и ответ — false.

Prompt
Напишите программу на TypeScript с типом User (name и age) и массивом, содержащим Alice 24, Bob 31 и Charlie 29. Используйте обобщённый помощник isSorted с функцией сравнения, которая смотрит только на возраст, и выведите, упорядочены ли пользователи по возрасту.
Вывод: программа на TypeScript и захваченный вывод
Check order by a field

Обратный порядок

Переворачивание ставит последний элемент первым, а первый элемент — последним.

reverse() переворачивает массив на месте, точно так же, как это делает sort(). Чтобы вместо этого получить перевёрнутую копию, переверните копию: [...words].reverse(). Оригинал сохраняет свой порядок.

Перевёрнутая копия

Копия переворачивается и выводится, затем выводится оригинал, чтобы показать, что он не изменился.

Prompt
Напишите программу на TypeScript, которая берёт слова one, two, three, four и переворачивает их копию с помощью [...words].reverse(), так что оригинал сохраняет свой порядок. Выведите перевёрнутую копию, а затем оригинал.
Вывод: программа на TypeScript и захваченный вывод
Reversed copy

Случайный порядок

Перемешивание ставит элементы в случайном порядке.

Метода перемешивания нет. Обычный способ — небольшой цикл, который идёт по массиву с последней позиции вниз и меняет каждый элемент местами с элементом на более ранней позиции. Цикл меняет тот массив, который ему дали, поэтому перемешивайте копию, когда оригинал вам ещё нужен.

Math.random() давал бы другой порядок при каждом запуске. Поэтому в этом примере создаётся собственный генератор: две строки арифметики, которые превращают seed в поток чисел от 0 до 1. Один и тот же seed всегда даёт один и тот же порядок, что полезно для тестов и для примеров вроде этого.

Перемешанная копия

Генератор с seed 9 перемешивает копию слов и выводит [ 'two', 'four', 'three', 'one' ]. Повторный запуск программы выводит тот же порядок, потому что генератор стартует с того же seed.

Prompt
Напишите программу на TypeScript с маленьким генератором случайных чисел с seed, чтобы вывод был одинаковым при каждом запуске. Используйте его, чтобы перемешать копию слов one, two, three, four циклом, который идёт с последней позиции вниз и меняет каждый элемент местами с элементом на более ранней позиции, и выведите перемешанную копию.
Вывод: программа на TypeScript и захваченный вывод
Shuffled copy