So erhalten Sie Indizes eines sortierten Arrays in Python

199

Ich habe eine numerische Liste:

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

Nun, wenn ich diese Liste sortiere, um zu erhalten [1, 2, 3, 5, 100]. Was ich möchte, sind die Indizes der Elemente aus der ursprünglichen Liste in der sortierten Reihenfolge, dh [0, 1, 2, 4, 3] --- ala MATLABs Sortierfunktion, die sowohl Werte als auch Indizes zurückgibt.

Gyan
quelle
2
Siehe auch
kevinarpe
@unutbu Dies ist kein Betrug (IMO). Die Frage widerspricht nicht der Verwendung von Numpy.argsort ()
amit
@amit: Was meinst du mit "widerspricht nicht"?
Unutbu
@unutbu Numpy.argsort () ist eine gute Antwort auf diese Frage. Es könnte ein Betrug mit dem anderen Thread sein, der verlinkt ist (den Sie ebenfalls geschlossen haben und den ich nicht haben sollte), aber nicht mit dem, den Sie als Numpy erwähnt haben. argsort () ist eine gute Antwort für diese beiden, aber NICHT für die, auf die Sie sich bezogen haben.
Amit
1
Leider weist diese Frage einen schwerwiegenden Fehler bei der Auswahl des Beispiels auf, da zwei verschiedene Arten des Lesens der Frage dieselbe Antwort geben würden, wenn die Eingabe nur eine Umsetzung in nicht sortierter Reihenfolge ist.

Antworten:

147

So etwas wie das nächste:

>>> 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) gibt Ihnen eine Liste mit Tupeln von (Index, Wert):

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

Sie sortieren die Liste, indem Sie sie an übergeben sortedund eine Funktion zum Extrahieren des Sortierschlüssels angeben (das zweite Element jedes Tupels; dafür lambdaist das vorgesehen. Schließlich wird der ursprüngliche Index jedes sortierten Elements mithilfe des [i[0] for i in ...]Listenverständnisses extrahiert .

Roman Bodnarchuk
quelle
7
Sie können itemgetter(1)anstelle der Lambda-Funktion verwenden
John La Rooy
4
@gnibbler bezieht sich auf die itemgetterFunktion im operatorModul, FYI. Also mach from operator import itemgetteres.
Lauritz V. Thaulow
1
Sie können die sortierte Liste und Angaben mit zip erhalten:sorted_items, sorted_inds = zip(*sorted([(i,e) for i,e in enumerate(my_list)], key=itemgetter(1)))
Charles L.
@RomanBodnarchuk das funktioniert nicht, x = [3,1,2]; numpy.argsort(x)ergibt [1,2,0].
shahar_m
24

Die Antworten mit enumeratesind nett, aber ich persönlich mag das Lambda nicht, das verwendet wird, um nach dem Wert zu sortieren. Im Folgenden werden nur der Index und der Wert umgekehrt und sortiert. Es wird also zuerst nach Wert und dann nach Index sortiert.

sorted((e,i) for i,e in enumerate(myList))
Ant6n
quelle
11

Aktualisierte Antwort mit enumerateund itemgetter:

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

Zippen Sie die Listen zusammen: Das erste Element im Tupel enthält den Index, das zweite den Wert (sortieren Sie ihn dann nach dem zweiten Wert des Tupels x[1] , x ist das Tupel).

Oder mit itemgetteraus dem operatorModul`:

from operator import itemgetter
sorted(enumerate(a), key=itemgetter(1))
Matt
quelle
1
Aufzählung scheint in diesem Fall angemessener als zip
njzk2
10

Ich habe diese mit perfplot (einem meiner Projekte) schnell überprüft und festgestellt, dass es schwierig ist, etwas anderes als numpy zu empfehlen (beachten Sie die Protokollskala):

Geben Sie hier die Bildbeschreibung ein


Code zur Reproduktion der Handlung:

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)",
    logx=True,
    logy=True,
)
Nico Schlömer
quelle
6

Wenn Sie numpy nicht verwenden möchten,

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

ist am schnellsten, wie hier gezeigt .

mab
quelle
5

Im Wesentlichen müssen Sie eine durchführen argsort. Welche Implementierung Sie benötigen, hängt davon ab, ob Sie externe Bibliotheken (z. B. NumPy) verwenden möchten oder ob Sie Pure-Python ohne Abhängigkeiten bleiben möchten.

Die Frage, die Sie sich stellen müssen, lautet: Wollen Sie die

  • Indizes, die das Array / die Liste sortieren würden
  • Indizes, die die Elemente im sortierten Array / in der sortierten Liste haben würden

Leider macht das Beispiel in der Frage nicht klar, was gewünscht wird, da beide das gleiche Ergebnis liefern:

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

Auswahl der argsortImplementierung

Wenn Sie NumPy zur Verfügung haben, können Sie einfach die Funktion numpy.argsortoder Methode verwendennumpy.ndarray.argsort .

Eine Implementierung ohne NumPy wurde bereits in einigen anderen Antworten erwähnt, daher werde ich hier nur die schnellste Lösung gemäß der Benchmark-Antwort zusammenfassen

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

Abrufen der Indizes, die das Array / die Liste sortieren würden

Um die Indizes zu erhalten, die das Array / die Liste sortieren würden, können Sie einfach argsortdas Array oder die Liste aufrufen . Ich verwende hier die NumPy-Versionen, aber die Python-Implementierung sollte die gleichen Ergebnisse liefern

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

Das Ergebnis enthält die Indizes, die zum Abrufen des sortierten Arrays erforderlich sind.

Da das sortierte Array [1, 2, 3, 4]das argsortierte Array wäre, enthält es die Indizes dieser Elemente im Original.

  • Der kleinste Wert ist 1und befindet sich am Index 1im Original, sodass das erste Element des Ergebnisses ist1 .
  • Das 2ist 2im Original im Index, also ist das zweite Element des Ergebnisses 2.
  • Das 3ist 0im Original im Index, also ist das dritte Element des Ergebnisses 0.
  • Der größte Wert 4und der Index 3im Original, also das letzte Element des Ergebnisses 3.

Abrufen der Indizes, die die Elemente im sortierten Array / in der sortierten Liste haben würden

In diesem Fall müssten Sie sich argsort zweimal bewerben :

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

In diesem Fall :

  • Das erste Element des Originals ist 3der drittgrößte Wert, sodass es einen Index 2im sortierten Array / der sortierten Liste hat, also das erste Element 2.
  • Das zweite Element des Originals ist 1der kleinste Wert, sodass es einen Index 0im sortierten Array / der sortierten Liste hat, also das zweite Element 0.
  • Das dritte Element des Originals ist 2der zweitkleinste Wert, sodass es einen Index 1im sortierten Array / der sortierten Liste hat, also das dritte Element 1.
  • Das vierte Element des Originals ist 4der größte Wert, sodass es einen Index 3im sortierten Array / der sortierten Liste hat, also das letzte Element 3.
MSeifert
quelle
4

Die anderen Antworten sind falsch.

argsortEinmal laufen ist nicht die Lösung. Zum Beispiel der folgende Code:

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

Renditen, array([1, 2, 0], dtype=int64)die wir nicht wollen.

Die Antwort sollte sein, argsortzweimal zu laufen :

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

gibt array([2, 0, 1], dtype=int64)wie erwartet.

shahar_m
quelle
Ihr Anspruch macht x[2](3) das kleinste Element und x[1](1) das größte Element (da das Sortieren von ganzen Zahlen sie vom kleinsten zum größten Wert ordnet). Mit dem OPs-Beispiel np.argsort([1, 2, 3, 100, 5])ergibt sich auch eine einzelne Rendite array([0, 1, 2, 4, 3]), bei der es sich anscheinend um die vom OP gewünschten Indizes handelt.
0 0
1
@ 0 0 Ihr Beispiel ist ein spezieller Fall. Wenn wir rennen arr = [1,2,3,100, 5, 9] res = np.argsort(arr) print(res), bekommen wir [0 1 2 4 5 3]was falsch ist.
shahar_m
Ich bin mir nicht sicher , was falsch ist: arr[res]Erträge array([ 1, 2, 3, 5, 9, 100]), die vollkommen in Ordnung zu sein scheinen, da das resultierende Array in (zunehmender) Reihenfolge ist.
0 0
@ 0 0 für arr=[1,2,3,100, 5, 9], ich erwarte die Ausgabe inds=[0,1,2,5,3,4], weil dies die Reihenfolge ist, in der Sie die Elemente (zunehmend) ordnen - 1 ist an der 0er Stelle, 2 an der 1. Stelle, ...., 5 an der 3. Platz und 9 auf dem 4. Platz. Um diese Ausgabe ( inds) zu erhalten, muss ich argsort, wie bereits erwähnt, zweimal ausgeführt werden.
shahar_m
Diese Indizes sind also eine Art Rangfolge der Array-Elemente (0. Platz, 1. Platz usw.). Angesichts der Erwähnung des OP gegenüber MATLABssort möchte das OP die andere Funktionalität, wie sie np.argsortnormalerweise verwendet wird (wo man arr[np.argsort[arr]]das sortierte Array erhalten kann, wie im letzten MATLAB-Beispiel). Ihre Antwort gilt stattdessen für diesen Fall / diese Frage .
0 0
0

Importiere numpy als np

FÜR 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 Gibt die Indizes von S in sortierter Reihenfolge zurück

FÜR WERT

np.sort(S)

[output]=array([ 0,  2,  3, 10, 11, 33, 44, 55, 66])
negi
quelle
0

Wir werden ein weiteres Array von Indizes von 0 bis n-1 erstellen. Dann komprimieren Sie dieses in das ursprüngliche Array und sortieren es dann anhand der ursprünglichen Werte

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

`

Jai Dewani
quelle