Урок курса
Сортировка массивов: sort и argsort
NumPy и pandas: практический тренажёрЧто возвращают sort и argsort
np.sort(a) возвращает отсортированную копию массива. np.argsort(a) возвращает индексы, по которым элементы идут в порядке сортировки: их можно применить и к связанному массиву.
import numpy as np
a = np.array([30, 10, 20])
order = np.argsort(a)
print(np.sort(a).tolist())
print(order.tolist())
print(a[order].tolist())
print(np.sort(a)[::-1].tolist())
print(a.tolist())Результат:
[10, 20, 30]
[1, 2, 0]
[10, 20, 30]
[30, 20, 10]
[30, 10, 20]Последняя строка показывает, что исходный массив не изменился. Метод a.sort(), напротив, сортирует массив на месте и возвращает None. Обратный срез выше подходит для одномерного примера; для матриц учитывайте выбранную ось.
В прошлом уроке мы разбирали, как объединять массивы через concatenate и stack, и постоянно проверяли результат через .shape — это был главный способ убедиться, что склейка прошла корректно. Сейчас переходим к сортировке, и параметр axis здесь работает по той же логике: он указывает, вдоль какой оси идёт операция.
np.sort: получение отсортированной копии по оси
Функция np.sort возвращает новый массив с теми же элементами, расположенными в порядке возрастания. Исходный массив при этом не трогается — это важно запомнить сразу, потому что в numpy существует и метод .sort(), который сортирует массив «на месте». Здесь речь только о функции-копии.
Для одномерного массива всё просто:
import numpy as np
arr = np.array([30, 10, 40, 20])
sorted_arr = np.sort(arr)
print(sorted_arr) # [10 20 30 40]
print(arr) # [30 10 40 20] — не изменился
Shape результата совпадает с shape входа: был (4,), стал (4,).
С двумерным массивом поведение определяется параметром axis.
M = np.array([[3, 1, 4],
[1, 5, 2]])
axis=0 — каждый столбец сортируется независимо, элементы упорядочиваются сверху вниз:
print(np.sort(M, axis=0))
# [[1 1 2]
# [3 5 4]]
Столбец 0: [3, 1] → [1, 3]. Столбец 1: [1, 5] → [1, 5]. Столбец 2: [4, 2] → [2, 4]. Строки при этом как единицы не сохраняются — элементы из одной строки могут «разойтись» по разным позициям.
axis=1 — каждая строка сортируется независимо, элементы упорядочиваются слева направо:
print(np.sort(M, axis=1))
# [[1 3 4]
# [1 2 5]]
Строка 0: [3, 1, 4] → [1, 3, 4]. Строка 1: [1, 5, 2] → [1, 2, 5].
В обоих случаях shape результата остаётся (2, 3) — таким же, как у исходного M. np.sort никогда не меняет форму массива при использовании числового axis.
Практическое правило выбора оси: определите, какие последовательности должны быть отсортированы. Если нужно упорядочить элементы внутри каждой строки — используйте axis=1. Если нужно упорядочить элементы внутри каждого столбца — используйте axis=0. Нужная ось зависит от структуры ваших данных и задачи.
np.argsort: индексы порядка элементов
np.sort отвечает на вопрос «каковы значения в отсортированном порядке?». np.argsort отвечает на другой вопрос: «в каком порядке нужно взять элементы исходного массива, чтобы они оказались отсортированы?». Результат — не значения, а целочисленные индексы.
Для одномерного массива:
import numpy as np
arr = np.array([30, 10, 40, 20])
print(np.argsort(arr)) # [1 3 0 2]
Читается так: на первом месте стоит элемент с индексом 1 (это 10), на втором — с индексом 3 (это 20), на третьем — с индексом 0 (это 30), на четвёртом — с индексом 2 (это 40). Если применить этот результат обратно к arr, получим отсортированный массив:
idx = np.argsort(arr)
print(arr[idx]) # [10 20 30 40]
То есть np.argsort — это не альтернативный способ получить те же [10, 20, 30, 40], а способ узнать откуда они пришли.
Для двумерного массива поведение управляется параметром axis, как и у np.sort.
M = np.array([[3, 1, 4],
[1, 5, 2]])
print(np.argsort(M, axis=1))
# [[1 0 2]
# [0 2 1]]
При axis=1 каждая строка обрабатывается независимо. В строке 0 значения [3, 1, 4]: наименьшее — на позиции 1 (это 1), затем позиция 0 (это 3), затем позиция 2 (это 4). В строке 1 значения [1, 5, 2]: порядок — 0, 2, 1. Каждый индекс — это позиция внутри своей строки.
При axis=0 картина другая: каждый столбец сортируется независимо, а индексы показывают номер строки внутри своего столбца.
print(np.argsort(M, axis=0))
# [[1 0 1]
# [0 1 0]]
Столбец 0: значения [3, 1] — наименьшее в строке 1, большее в строке 0. Столбец 1: [1, 5] — наименьшее в строке 0. Столбец 2: [4, 2] — наименьшее в строке 1.
Главное отличие от np.sort: результат np.argsort сам по себе нельзя интерпретировать как данные — это карта перестановки, инструмент для последующей индексации.
Shape результатов sort и argsort: анализ по осям
Одно свойство объединяет np.sort и np.argsort: обе функции сохраняют форму входного массива. Какую бы ось вы ни указали, shape результата будет совпадать с shape входа.
Проверим это на конкретных числах.
import numpy as np
# Одномерный массив shape (4,)
arr = np.array([30, 10, 40, 20])
print(np.sort(arr).shape) # (4,)
print(np.argsort(arr).shape) # (4,)
# Двумерный массив shape (2, 3)
M = np.array([[3, 1, 4],
[1, 5, 2]])
print(np.sort(M, axis=0).shape) # (2, 3)
print(np.sort(M, axis=1).shape) # (2, 3)
print(np.argsort(M, axis=0).shape) # (2, 3)
print(np.argsort(M, axis=1).shape) # (2, 3)
Во всех случаях результат — тот же (4,) или (2, 3). Смена axis меняет смысл операции (что с чем сравнивается), но не форму выхода.
Почему это важно? Потому что после сортировки или получения индексов вы можете сразу применять результат к другим операциям, не пересчитывая shape вручную. Если вошёл массив (2, 3), вышел массив (2, 3) — это гарантия, а не случайность.
Отдельно стоит отметить: это справедливо только при указании числового axis — 0, 1 и так далее. Есть режим axis=None, который сначала разворачивает массив в одномерный и сортирует его целиком, — но в этом уроке он не используется, и его поведение принципиально другое.
Практический способ проверки: после любого вызова np.sort или np.argsort сразу смотрите на .shape результата. Если входной массив имел shape (r, c), выходной должен тоже показать (r, c). Расхождение означает, что где-то затесался axis=None или лишний вызов flatten.
Перестановка связанного массива через argsort и fancy indexing
Теперь, когда понятно, что возвращает np.argsort, можно использовать этот результат как инструмент. Типичная задача: есть два массива одинаковой длины — один с числовыми значениями, другой со связанными метками. Нужно отсортировать их согласованно, чтобы порядок меток соответствовал порядку значений.
Пример: массив scores содержит результаты пяти участников, массив names — их имена. Хочется расставить имена по возрастанию результата.
import numpy as np
scores = np.array([72, 45, 91, 60, 83])
names = np.array(['Алиса', 'Борис', 'Вера', 'Глеб', 'Дина'])
idx = np.argsort(scores)
print(idx) # [1 3 0 4 2]
print(scores[idx]) # [45 60 72 83 91]
print(names[idx]) # ['Борис' 'Глеб' 'Алиса' 'Дина' 'Вера']
Что происходит: np.argsort(scores) вычисляет, в каком порядке нужно взять элементы scores, чтобы они шли по возрастанию. Этот порядок записывается в idx. Затем тот же idx применяется к names через fancy indexing — и массив имён переставляется точно в том же порядке.
Оба массива остаются неизменными — мы получаем две новые перестановки. Никаких циклов, никакого ручного отслеживания позиций.
Важные условия, при которых это работает:
- оба массива одномерные;
- оба имеют одинаковую длину;
idxполучен изnp.argsortодного из них.
Если эти условия выполнены, arr[idx] и labels[idx] дадут результаты с тем же shape (n,), что и исходные массивы, и их элементы будут попарно соответствовать друг другу.
Эта техника часто встречается, когда нужно ранжировать объекты по одному признаку, сохраняя при этом их идентификаторы или другие атрибуты. Позже, в pandas, аналогичную задачу решает sort_values для DataFrame — но там это происходит под капотом, а здесь вы видите механику явно.
Задача 1
Массив arr = np.array([50, 20, 40, 10, 30]). Что вернёт np.argsort(arr)?
Задача 2
Двумерный массив A имеет shape (4, 5). Чему равен shape результата np.argsort(A, axis=0)?
Задача 3
Турнирная таблица: сортировка участников по оценке через argsort
Рейтинг олимпиады
Организационный комитет ежегодной городской олимпиады по математике завершил проверку работ. Каждый участник получил целочисленную оценку — количество набранных баллов. Оценки у всех разные: система проверки устроена так, что ничья невозможна. Теперь нужно сформировать итоговую турнирную таблицу: список участников, упорядоченный от наименьшего результата к наибольшему.
Почему от наименьшего? Оргкомитет придерживается традиции «восходящего рейтинга»: в финальном протоколе первым идёт участник с минимальным баллом, а победитель — в самом конце. Это удобно для последующей рассылки: письма с поздравлениями отправляются в обратном порядке чтения таблицы, и победитель получает своё письмо последним — в торжественный момент церемонии.
Откуда берутся данные
Секретарь олимпиады формирует два списка в том порядке, в котором участники сдавали работы:
- список имён участников;
- список оценок — по одной на каждого участника, в той же позиции.
Например, если Алиса сдала работу первой и получила 72 балла, то в списке имён на первом месте стоит «Алиса», а в списке оценок на первом месте стоит 72. Важно, что порядок сдачи работ не несёт смысловой нагрузки — он случаен.
Что требуется от программы
Программа-помощник секретаря должна принять оба списка и выдать четыре строки итогового протокола.
Первая строка — это перестановочный индекс: для каждой позиции в отсортированном рейтинге указывается, какой номер (считая с нуля) имел соответствующий участник в исходном списке сдачи работ. Эта строка нужна техническому отделу для сверки с архивом бумажных работ.
Вторая строка — отсортированные оценки участников: от минимальной к максимальной. Бухгалтерия использует её для расчёта призового фонда.
Третья строка — имена участников в том же порядке, что и отсортированные оценки. Это и есть сам рейтинг, который вывешивается на доску объявлений.
Четвёртая строка — техническая метка формата: она фиксирует размерность каждого из трёх результирующих массивов. Системный администратор использует её для автоматической валидации файла перед загрузкой в базу данных. Метка имеет строго определённый вид: shape idx=(n,) scores_sorted=(n,) names_sorted=(n,), где вместо n подставляется конкретное число участников.
Важное техническое ограничение
Программа не должна изменять исходные списки имён и оценок — они могут понадобиться секретарю позже в исходном виде (например, для формирования справок в порядке сдачи работ). Поэтому сортировка выполняется через вычисление индекса перестановки (np.argsort), который возвращает новый массив и не затрагивает исходные данные.
Формальный контракт
Входные данные (stdin):
n
name_0 name_1 ... name_{n-1}
score_0 score_1 ... score_{n-1}
- Первая строка — целое число
n(количество участников). - Вторая строка —
nимён через пробел; читается как одномерный массивnp.array. - Третья строка —
nпопарно различных целых чисел через пробел (оценки); читается какnp.array(..., dtype=int).
Действия:
- Вычислить
idx = np.argsort(scores)— массив индексов, упорядочивающих оценки по возрастанию. - Применить
idxкscoresи кnamesчерез fancy indexing, получивscores_sortedиnames_sorted. - Убедиться, что исходные
scoresиnamesне изменились.
Выходные данные (stdout) — ровно четыре строки:
idx_0 idx_1 ... idx_{n-1}
score_sorted_0 score_sorted_1 ... score_sorted_{n-1}
name_sorted_0 name_sorted_1 ... name_sorted_{n-1}
shape idx=(n,) scores_sorted=(n,) names_sorted=(n,)
- Элементы в строках 1–3 разделены одиночными пробелами.
- В строке 4 вместо
nстоит конкретное число участников, например(5,)приn=5. - Никаких дополнительных строк, пробелов в конце строк или пустых строк выводить не нужно.
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 4
np.sort по двум осям: shape результата и неизменность исходного массива
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinдва целых числаRиC— число строк и столбцов. - Читает следующие
Rстрок; каждая содержит ровноCцелых чисел через пробел. Создаёт двумерный массивMчерезnp.array(..., dtype=int)формы(R, C). - До любых сортировок сохраняет копию:
M_orig = M.copy(). - Вычисляет
s0 = np.sort(M, axis=0)— каждый столбец отсортирован по возрастанию. - Вычисляет
s1 = np.sort(M, axis=1)— каждая строка отсортирована по возрастанию. - Выводит три последовательных блока, всего
2R + 3строк:
- блок 1: строка
s0 shape=<shape>, затемRстрок матрицыs0; - блок 2: строка
s1 shape=<shape>, затемRстрок матрицыs1; - блок 3: одна строка
unchanged=<bool>—True, еслиMне был изменён, то естьnp.array_equal(M, M_orig), иначеFalse.
Точный формат вывода показан в разделе «Ввод и вывод».
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
R C
row_0_col_0 row_0_col_1 ... row_0_col_{C-1}
...
row_{R-1}_col_0 ... row_{R-1}_col_{C-1}
Формат вывода
s0 shape=(R, C)
<строки матрицы s0, каждая — значения через пробел>
s1 shape=(R, C)
<строки матрицы s1, каждая — значения через пробел>
unchanged=<True или False>
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
argsort по axis=0: ранги объектов в каждом столбце
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinдва целых числаRиC— число строк и столбцов матрицы. - Читает следующие
Rстрок; каждая содержит ровноCцелых чисел через пробел. Создаёт двумерный массивMчерезnp.array(..., dtype=int)формы(R, C). - Вычисляет
ranks = np.argsort(M, axis=0). Каждый элементranks[i, j]— это номер строки исходной матрицы, которая занимаетi-ю позицию по возрастанию в столбцеj. - Выводит в
stdoutтри блока:
- строка 1:
ranks shape=<shape>— shape результатаnp.argsort(M, axis=0); - строки 2..R+1: матрицу
ranks— каждую строку на отдельной строке, значения через пробел; - строка R+2:
original unchanged=<bool>—True, еслиMне был изменён (сравните с копией, сделанной до вычисленияranks), иначеFalse.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
R C
row_0_col_0 row_0_col_1 ... row_0_col_{C-1}
...
row_{R-1}_col_0 ... row_{R-1}_col_{C-1}
Формат вывода
ranks shape=(R, C)
элементы ranks строка за строкой
original unchanged=True
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Найди и исправь: перепутаны axis в np.sort и индексы vs значения в argsort
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе есть сломанный код. В нём допущены ровно три ошибки в трёх независимых строках:
- Первый вызов
np.sortиспользуетaxis=1, хотя нужно отсортировать каждый столбец по возрастанию (то есть нуженaxis=0). - Второй вызов
np.sortиспользуетaxis=0, хотя нужно отсортировать каждую строку по возрастанию (то есть нуженaxis=1). - Вместо применения результата
np.argsortк исходному массиву через fancy indexing код выводит сами индексыidxвместо переставленных значенийvals[idx].
Каждая ошибка — в отдельной строке. Исправьте ровно три строки.
Что нужно сделать
Исправьте три ошибочные строки так, чтобы программа:
- Читала из
stdinдва целых числаRиC. - Читала следующие
Rстрок поCцелых чисел — двумерный массивMформы(R, C). - Читала ещё одну строку с
Cцелыми числами — одномерный массивvalsдлиныC. - Вычисляла
col_sorted = np.sort(M, axis=0)— каждый столбецMотсортирован по возрастанию, shape(R, C). - Вычисляла
row_sorted = np.sort(M, axis=1)— каждая строкаMотсортирована по возрастанию, shape(R, C). - Вычисляла
idx = np.argsort(vals)и выводилаvals[idx]— значенияvalsв отсортированном порядке. - Выводила в
stdoutровно три блока:
- строка
col_sorted shape=(R, C), затемRстрок матрицыcol_sorted; - строка
row_sorted shape=(R, C), затемRстрок матрицыrow_sorted; - строка
vals sorted: <значения через пробел>.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
R C
row_0_col_0 row_0_col_1 ... row_0_col_{C-1}
...
row_{R-1}_col_0 ... row_{R-1}_col_{C-1}
vals_0 vals_1 ... vals_{C-1}
Формат вывода
col_sorted shape=(R, C)
<строки col_sorted через пробел>
row_sorted shape=(R, C)
<строки row_sorted через пробел>
vals sorted: vals_sorted_0 vals_sorted_1 ...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- NumPy sort по axis=0 и axis=1 без изменения исходного массива
Отсортируйте двумерный массив по двум осям через np.sort и проверьте форму и неизменность исходных данных.
- NumPy argsort по axis=0: индексы сортировки столбцов
Что возвращает np.argsort и как читать индексы по axis=0? Пример применения индексов и задача на сортировку столбцов.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
