Как получить индексы отсортированного массива в Python

200

У меня есть числовой список:

myList = [1, 2, 3, 100, 5]

Теперь, если я сортирую этот список, чтобы получить [1, 2, 3, 5, 100]. То, что я хочу, это индексы элементов из исходного списка в отсортированном порядке, т.е. [0, 1, 2, 4, 3] --- функция сортировки MATLAB, которая возвращает и значения, и индексы.

Gyan
источник
2
Связанный: stackoverflow.com/questions/7851077/…
kevinarpe
@unutbu Это не дурак (ИМО). Вопрос не противоречит использованию Numpy.argsort ()
amit
@amit: Что вы подразумеваете под "не противоречит"?
Unutbu
@unutbu Numpy.argsort () - хороший ответ на этот вопрос, возможно, это обман на другой связанный поток (который вы также закрыли, и я думаю, вы не должны иметь), но не тот, который вы упомянули, как Numpy. argsort () является хорошим ответом для этих двух, но НЕ для того, на кого вы ссылались.
amit
1
К сожалению, у этого вопроса есть серьезный недостаток в выборе примера, так как два разных способа чтения вопроса дают один и тот же ответ, когда входные данные представляют собой просто транспонирование из отсортированного порядка.

Ответы:

189

Если вы используете numpy, у вас есть доступная функция argsort ():

>>> import numpy
>>> numpy.argsort(myList)
array([0, 1, 2, 4, 3])

http://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html

Это возвращает аргументы, которые будут сортировать массив или список.

Мэтью Льюис
источник
Обратите внимание, что это может быть не то, что вы хотите! См. Этот вопрос: stackoverflow.com/questions/54388972/…
Брэм Ванрой
147

Примерно так:

>>> myList = [1, 2, 3, 100, 5]
>>> [i[0] for i in sorted(enumerate(myList), key=lambda x:x[1])]
[0, 1, 2, 4, 3]

enumerate(myList) выдает список, содержащий кортежи (индекс, значение):

[(0, 1), (1, 2), (2, 3), (3, 100), (4, 5)]

Вы сортируете список, передавая его sortedи определяя функцию для извлечения ключа сортировки (второй элемент каждого кортежа; для этого и нужен lambda. Наконец, исходный индекс каждого отсортированного элемента извлекается с использованием [i[0] for i in ...]понимания списка.

Роман Боднарчук
источник
7
Вы можете использовать itemgetter(1)вместо лямбда-функции
Джон Ла Рой
4
@gnibbler относится к itemgetterфункции в operatorмодуле, FYI. Так что, from operator import itemgetterчтобы использовать это.
Лауриц В. Таулов
1
Вы можете получить отсортированный список и указатели, используя почтовый индекс:sorted_items, sorted_inds = zip(*sorted([(i,e) for i,e in enumerate(my_list)], key=itemgetter(1)))
Чарльз Л.
@RomanBodnarchuk это не работает, x = [3,1,2]; numpy.argsort(x)дает [1,2,0].
shahar_m
24

Ответы enumerateхороши, но лично мне не нравится лямбда, используемая для сортировки по значению. Следующее просто инвертирует индекс и значение и сортирует их. Так что сначала он будет отсортирован по значению, а затем по индексу.

sorted((e,i) for i,e in enumerate(myList))
Ant6n
источник
11

Обновленный ответ с enumerateи itemgetter:

sorted(enumerate(a), key=lambda x: x[1])
# [(0, 1), (1, 2), (2, 3), (4, 5), (3, 100)]

Сжать списки вместе: первый элемент в кортеже будет индексом, второй - значением (затем отсортируйте его, используя второе значение кортежа x[1] , x - кортеж)

Или используя itemgetterиз operatorмодуля`:

from operator import itemgetter
sorted(enumerate(a), key=itemgetter(1))
Matt
источник
1
перечислять кажется более подходящим, чем zip в этом случае
njzk2
10

Я быстро проверил их производительность с помощью perfplot ( мой проект) и обнаружил, что трудно рекомендовать что-либо еще, кроме numpy (обратите внимание на масштаб журнала):

введите описание изображения здесь


Код для воспроизведения сюжета:

import perfplot
import numpy


def sorted_enumerate(seq):
    return [i for (v, i) in sorted((v, i) for (i, v) in enumerate(seq))]


def sorted_enumerate_key(seq):
    return [x for x, y in sorted(enumerate(seq), key=lambda x: x[1])]


def sorted_range(seq):
    return sorted(range(len(seq)), key=seq.__getitem__)


def numpy_argsort(x):
    return numpy.argsort(x)


perfplot.save(
    "argsort.png",
    setup=lambda n: numpy.random.rand(n),
    kernels=[sorted_enumerate, sorted_enumerate_key, sorted_range, numpy_argsort],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(x)",
)
Нико Шлёмер
источник
6

Если вы не хотите использовать NumPy,

sorted(range(len(seq)), key=seq.__getitem__)

самый быстрый, как показано здесь .

МАБ
источник
5

По сути, вам нужно сделать argsort , какая реализация вам нужна, зависит от того, хотите ли вы использовать внешние библиотеки (например, NumPy) или если вы хотите остаться чистым Python без зависимостей.

Вопрос, который вы должны задать себе: вы хотите

  • индексы, которые будут сортировать массив / список
  • индексы, которые элементы будут иметь в отсортированном массиве / списке

К сожалению, пример в вопросе не проясняет, что нужно, потому что оба будут давать один и тот же результат:

>>> arr = np.array([1, 2, 3, 100, 5])

>>> np.argsort(np.argsort(arr))
array([0, 1, 2, 4, 3], dtype=int64)

>>> np.argsort(arr)
array([0, 1, 2, 4, 3], dtype=int64)

Выбор argsort реализации

Если у вас есть NumPy, вы можете просто использовать функцию numpy.argsortили метод numpy.ndarray.argsort.

Реализация без NumPy уже упоминалась в некоторых других ответах, поэтому я просто напомню самое быстрое решение в соответствии с ответом на тестирование здесь

def argsort(l):
    return sorted(range(len(l)), key=l.__getitem__)

Получение индексов, которые будут сортировать массив / список

Чтобы получить индексы, которые будут сортировать массив / список, вы можете просто вызвать argsortмассив или список. Я использую версии NumPy здесь, но реализация Python должна давать те же результаты

>>> arr = np.array([3, 1, 2, 4])
>>> np.argsort(arr)
array([1, 2, 0, 3], dtype=int64)

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

Поскольку отсортированный массив будет массивом [1, 2, 3, 4]argsorted, он содержит индексы этих элементов в оригинале.

  • Наименьшее значение равно 1индексу 1в оригинале, поэтому первым элементом результата является 1.
  • Индекс 2имеет 2оригинальное значение, поэтому второй элемент результата равен 2.
  • 3Имеет индекс 0в оригинале , так что третий элемент результата 0.
  • Наибольшее значение, 4и оно по индексу 3в оригинале, поэтому последний элемент результата 3.

Получение индексов, которые элементы будут иметь в отсортированном массиве / списке

В этом случае вам необходимо подать заявку argsort дважды :

>>> arr = np.array([3, 1, 2, 4])
>>> np.argsort(np.argsort(arr))
array([2, 0, 1, 3], dtype=int64)

В таком случае :

  • первый элемент оригинала is 3, который является третьим по величине значением, поэтому он будет иметь индекс 2в отсортированном массиве / списке, поэтому первый элемент2 .
  • второй элемент оригинала - 1это наименьшее значение, поэтому он будет иметь индекс 0в отсортированном массиве / списке, так что второй элемент - 0.
  • третий элемент оригинала - 2это второе по наименьшему значению, поэтому он будет иметь индекс 1в отсортированном массиве / списке, так что третий элемент 1.
  • четвертый элемент оригинала - 4это наибольшее значение, поэтому он будет иметь индекс 3в отсортированном массиве / списке, так что последний элемент 3.
MSeifert
источник
4

Другие ответы НЕПРАВИЛЬНЫ.

Запуск argsortодин раз не является решением. Например, следующий код:

import numpy as np
x = [3,1,2]
np.argsort(x)

дает array([1, 2, 0], dtype=int64)не то, что мы хотим.

Ответ должен быть запущен argsortдважды:

import numpy as np
x = [3,1,2]
np.argsort(np.argsort(x))

дает, array([2, 0, 1], dtype=int64)как и ожидалось.

shahar_m
источник
Ваша заявка делает x[2](3) наименьший элемент и x[1](1) наибольший элемент (поскольку целые числа сортировки упорядочивают их от наименьшего значения к наибольшему значению). Кроме того, в примере с OP, один np.argsort([1, 2, 3, 100, 5])выход array([0, 1, 2, 4, 3]), который, по-видимому, является индексами, которые хочет OP.
0 0
1
@ 0 0 Ваш пример - особый случай. Если мы бежим, arr = [1,2,3,100, 5, 9] res = np.argsort(arr) print(res)мы получаем, [0 1 2 4 5 3]что не так.
shahar_m
Мне непонятно, что не так: arr[res]выходы array([ 1, 2, 3, 5, 9, 100]), которые, кажется, совершенно нормально, так как этот результирующий массив находится в (возрастающем) порядке.
0 0
@ 0 0 для arr=[1,2,3,100, 5, 9], я ожидаю, что результат будет inds=[0,1,2,5,3,4], потому что это порядок, в котором вы будете упорядочивать элементы (все чаще) - 1 находится на 0-м месте, 2 на 1-м месте, ...., 5 на 3 место и 9 место на 4 месте. Чтобы получить этот вывод ( inds), мне нужно запустить argsortдважды, как я уже упоминал.
shahar_m
Таким образом, эти индексы являются своего рода ранжированием элементов массива (0-е место, 1-е место и т. Д.). Учитывая упоминание OP в MATLABsort , я считаю, что OP хочет другую функциональность, очень похожую np.argsortна обычную (где можно использовать arr[np.argsort[arr]]отсортированный массив, как в последнем примере MATLAB). Ваш ответ относится к этому делу / вопросу вместо.
0 0
0

Импортировать numpy как np

ДЛЯ ИНДЕКСА

S=[11,2,44,55,66,0,10,3,33]

r=np.argsort(S)

[output]=array([5, 1, 7, 6, 0, 8, 2, 3, 4])

argsort Возвращает индексы S в отсортированном порядке.

НА СТОИМОСТЬ

np.sort(S)

[output]=array([ 0,  2,  3, 10, 11, 33, 44, 55, 66])
неги
источник
0

Мы создадим еще один массив индексов от 0 до n-1. Затем заархивируем его в исходный массив и затем отсортируем его на основе исходных значений.

ar = [1,2,3,4,5]
new_ar = list(zip(ar,[i for i in range(len(ar))]))
new_ar.sort()

`

Джай Девани
источник