268

I have a numerical list:

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

Now if I sort this list to obtain [1, 2, 3, 5, 100]. What I want is the indices of the elements from the original list in the sorted order i.e. [0, 1, 2, 4, 3] --- ala MATLAB's sort function that returns both values and indices.

Martijn Pieters
  • 963,270
  • 265
  • 3,804
  • 3,187
Gyan
  • 2,689
  • 2
  • 13
  • 3
  • 2
    Related: http://stackoverflow.com/questions/7851077/how-to-return-index-of-a-sorted-list – kevinarpe Oct 25 '14 at 18:30
  • @unutbu This is not a dupe (IMO). The question does not contradict using Numpy.argsort() – amit Jun 04 '15 at 22:35
  • @amit: What do you mean by "does not contradict"? – unutbu Jun 04 '15 at 22:38
  • @unutbu Numpy.argsort() is a fine answer to this question, it might be a dupe to the other thread linked (which you also closed and I thin you shouldn't have) but not to the one you mentioned, as Numpy.argsort() is fine answer for these two, but NOT for the one you refered to. – amit Jun 04 '15 at 22:39
  • @amit: Please be more specific. Which page is not a dupe? By the way, http://stackoverflow.com/questions/3382352/equivalent-of-numpy-argsort-in-basic-python is showing how to define a **pure Python function** that *behaves like* numpy.argsort. It does not suggest np.argsort as the answer... – unutbu Jun 04 '15 at 22:40
  • @unutbu This is a dupe of http://stackoverflow.com/q/7851077/572670 Both should not be dupes of http://stackoverflow.com/q/3382352/572670 - since Numpy.argsort() is valid answer to them, but not to http://stackoverflow.com/q/3382352/572670 – amit Jun 04 '15 at 22:42
  • 1
    Unfortunately, this question has a severe flaw in its choice of example, as two different ways of reading the question would give the same answer when the input is just a transposition out of sorted order. –  Jun 05 '15 at 17:49
  • [order vs rank](https://stackoverflow.com/a/2315622) – djvg Jun 16 '21 at 12:44

15 Answers15

258

If you are using numpy, you have the argsort() function available:

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

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

This returns the arguments that would sort the array or list.

Zahra
  • 6,028
  • 9
  • 44
  • 69
Matthew Lewis
  • 2,742
  • 1
  • 14
  • 10
  • 3
    Note that this may not be what you want! See this question: https://stackoverflow.com/questions/54388972/getting-indices-of-ascending-order-of-list – Bram Vanroy Jan 27 '19 at 14:06
191

Something like next:

>>> 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) gives you a list containing tuples of (index, value):

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

You sort the list by passing it to sorted and specifying a function to extract the sort key (the second element of each tuple; that's what the lambda is for. Finally, the original index of each sorted element is extracted using the [i[0] for i in ...] list comprehension.

anton.burger
  • 5,579
  • 32
  • 48
Roman Bodnarchuk
  • 28,019
  • 11
  • 57
  • 73
  • 7
    you can use `itemgetter(1)` instead of the lambda function – John La Rooy Jun 21 '11 at 10:26
  • 4
    @gnibbler is referring to the [`itemgetter`](http://docs.python.org/library/operator.html#operator.itemgetter) function in the `operator` module, FYI. So do `from operator import itemgetter` to use it. – Lauritz V. Thaulow Jun 21 '11 at 11:12
  • 2
    you can get the sorted list and indicies by using zip: `sorted_items, sorted_inds = zip(*sorted([(i,e) for i,e in enumerate(my_list)], key=itemgetter(1)))` – Charles L. Nov 30 '15 at 02:58
  • 2
    @RomanBodnarchuk this doesn't work, `x = [3,1,2]; numpy.argsort(x)` yields [1,2,0]. – shahar_m May 14 '19 at 08:50
88
myList = [1, 2, 3, 100, 5]    
sorted(range(len(myList)),key=myList.__getitem__)

[0, 1, 2, 4, 3]
Cerbrus
  • 65,559
  • 18
  • 128
  • 140
robert king
  • 15,381
  • 8
  • 94
  • 111
42

I did a quick performance check on these with perfplot (a project of mine) and found that it's hard to recommend anything else but

np.argsort(x)

(note the log scale):

enter image description here


Code to reproduce the plot:

import perfplot
import numpy as np


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__)


b = perfplot.bench(
    setup=np.random.rand,
    kernels=[sorted_enumerate, sorted_enumerate_key, sorted_range, np.argsort],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(x)",
)
b.save("out.png")
Nico Schlömer
  • 46,467
  • 24
  • 178
  • 218
25

The answers with enumerate are nice, but I personally don't like the lambda used to sort by the value. The following just reverses the index and the value, and sorts that. So it'll first sort by value, then by index.

sorted((e,i) for i,e in enumerate(myList))
Ant6n
  • 1,748
  • 1
  • 17
  • 25
14

Updated answer with enumerate and itemgetter:

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

Zip the lists together: The first element in the tuple will the index, the second is the value (then sort it using the second value of the tuple x[1], x is the tuple)

Or using itemgetter from the operatormodule`:

from operator import itemgetter
sorted(enumerate(a), key=itemgetter(1))
Matt
  • 16,540
  • 7
  • 55
  • 70
10

Essentially you need to do an argsort, what implementation you need depends if you want to use external libraries (e.g. NumPy) or if you want to stay pure-Python without dependencies.

The question you need to ask yourself is: Do you want the

  • indices that would sort the array/list
  • indices that the elements would have in the sorted array/list

Unfortunately the example in the question doesn't make it clear what is desired because both will give the same result:

>>> 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)

Choosing the argsort implementation

If you have NumPy at your disposal you can simply use the function numpy.argsort or method numpy.ndarray.argsort.

An implementation without NumPy was mentioned in some other answers already, so I'll just recap the fastest solution according to the benchmark answer here

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

Getting the indices that would sort the array/list

To get the indices that would sort the array/list you can simply call argsort on the array or list. I'm using the NumPy versions here but the Python implementation should give the same results

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

The result contains the indices that are needed to get the sorted array.

Since the sorted array would be [1, 2, 3, 4] the argsorted array contains the indices of these elements in the original.

  • The smallest value is 1 and it is at index 1 in the original so the first element of the result is 1.
  • The 2 is at index 2 in the original so the second element of the result is 2.
  • The 3 is at index 0 in the original so the third element of the result is 0.
  • The largest value 4 and it is at index 3 in the original so the last element of the result is 3.

Getting the indices that the elements would have in the sorted array/list

In this case you would need to apply argsort twice:

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

In this case :

  • the first element of the original is 3, which is the third largest value so it would have index 2 in the sorted array/list so the first element is 2.
  • the second element of the original is 1, which is the smallest value so it would have index 0 in the sorted array/list so the second element is 0.
  • the third element of the original is 2, which is the second-smallest value so it would have index 1 in the sorted array/list so the third element is 1.
  • the fourth element of the original is 4 which is the largest value so it would have index 3 in the sorted array/list so the last element is 3.
MSeifert
  • 133,177
  • 32
  • 312
  • 322
7

If you do not want to use numpy,

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

is fastest, as demonstrated here.

mab
  • 2,418
  • 25
  • 36
7

The other answers are WRONG.

Running argsort once is not the solution. For example, the following code:

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

yields array([1, 2, 0], dtype=int64) which is not what we want.

The answer should be to run argsort twice:

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

gives array([2, 0, 1], dtype=int64) as expected.

shahar_m
  • 3,187
  • 4
  • 39
  • 58
  • Your claim makes `x[2]` (3) the smallest element, and `x[1]` (1) the largest element (since sorting integers orders them from smallest value to largest value). Also, with the OPs example, a single `np.argsort([1, 2, 3, 100, 5])` yields `array([0, 1, 2, 4, 3])`, which appears to be the indices the OP wants. – 9769953 Jan 17 '20 at 13:51
  • 1
    @0 0 your example is a specific case. If we run `arr = [1,2,3,100, 5, 9] res = np.argsort(arr) print(res)` then we get `[0 1 2 4 5 3]` which is wrong. – shahar_m Jan 19 '20 at 12:45
  • I'm unclear what is wrong: `arr[res]` yields `array([ 1, 2, 3, 5, 9, 100])`, which seems to be perfectly fine, since that resulting array is in (increasing) order. – 9769953 Jan 19 '20 at 14:21
  • @0 0 for `arr=[1,2,3,100, 5, 9]`, I expect the output to be `inds=[0,1,2,5,3,4]`, because this is the order in which you'll order the elements (increasingly) - 1 is in the 0s place, 2 in the 1st place, .... , 5 on the 3rd place and 9 on the 4th place. In order to get that output (`inds`) I need to run `argsort` twice, like I mentioned. – shahar_m Jan 20 '20 at 12:51
  • So those indices are a kind of ranking of the array elements (0th place, 1st place, etc). Given the OP's mention to [MATLAB's `sort`](https://www.mathworks.com/help/matlab/ref/sort.html), I reckon the OP wants the other functionality, much like `np.argsort` is normally used (where one can use `arr[np.argsort[arr]]` to obtain the sorted array, as in the last MATLAB example). Your answer applies to [this case / question](https://stackoverflow.com/questions/5284646/rank-items-in-an-array-using-python-numpy-without-sorting-array-twice) instead. – 9769953 Jan 20 '20 at 16:39
3

Most easiest way you can use Numpy Packages for that purpose:

import numpy
s = numpy.array([2, 3, 1, 4, 5])
sort_index = numpy.argsort(s)
print(sort_index)

But If you want that you code should use baisc python code:

s = [2, 3, 1, 4, 5]
li=[]
  
for i in range(len(s)):
      li.append([s[i],i])
li.sort()
sort_index = []
  
for x in li:
      sort_index.append(x[1])
  
print(sort_index)
2

We will create another array of indexes from 0 to n-1 Then zip this to the original array and then sort it on the basis of the original values

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

`

Jai dewani
  • 123
  • 1
  • 7
2
s = [2, 3, 1, 4, 5]
print([sorted(s, reverse=False).index(val) for val in s]) 

It works even for a list with duplicate elements.

Lorry
  • 79
  • 5
0

Import numpy as np

FOR INDEX

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 Returns the indices of S in sorted order

FOR VALUE

np.sort(S)

[output]=array([ 0,  2,  3, 10, 11, 33, 44, 55, 66])
negi
  • 378
  • 6
  • 12
0

Code:

s = [2, 3, 1, 4, 5]
li = []

for i in range(len(s)):
    li.append([s[i], i])
li.sort()
sort_index = []

for x in li:
    sort_index.append(x[1])

print(sort_index)

Try this, It worked for me cheers!

DRV
  • 510
  • 6
  • 17
0

firstly convert your list to this:

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

add a index to your list's item

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

next :

sorted(myList, key=lambda k:k[1])

result:

[[0, 1], [1, 2], [2, 3], [4, 5], [3, 100]]
J.Zhao
  • 2,112
  • 1
  • 11
  • 11