Liste nach Werten aus einer anderen Liste sortieren?

369

Ich habe eine Liste solcher Zeichenfolgen:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,   0,   1,   2,   2,   0,   1 ]

Was ist der kürzeste Weg, um X mit Werten von Y zu sortieren, um die folgende Ausgabe zu erhalten?

["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Die Reihenfolge der Elemente mit demselben "Schlüssel" spielt keine Rolle. Ich kann auf die Verwendung von forKonstrukten zurückgreifen, bin aber gespannt, ob es einen kürzeren Weg gibt. Irgendwelche Vorschläge?

Legende
quelle
Die Antwort von Riza kann beim Plotten von Daten hilfreich sein, da zip (* sortiert (zip (X, Y), Schlüssel = Lambda-Paar: Paar [0])) sowohl das sortierte X als auch das sortierte Y mit den Werten X zurückgibt.
jojo

Antworten:

479

Kürzester Code

[x for _,x in sorted(zip(Y,X))]

Beispiel:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Z = [x for _,x in sorted(zip(Y,X))]
print(Z)  # ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Allgemein gesagt

[x for _, x in sorted(zip(Y,X), key=lambda pair: pair[0])]

Erklärt:

  1. zipdie beiden lists.
  2. Erstellen Sie eine neue, sortierte listnach der zipVerwendung sorted().
  3. Extrahieren Sie mithilfe eines Listenverständnisses die ersten Elemente jedes Paares aus dem sortierten, komprimierten Element list.

Weitere Informationen zum Festlegen \ Verwenden des keyParameters sowie der sortedFunktion im Allgemeinen finden Sie hier .


Whatang
quelle
117
Dies ist korrekt, aber ich möchte den Hinweis hinzufügen, dass, wenn Sie versuchen, mehrere Arrays nach demselben Array zu sortieren, dies nicht unbedingt wie erwartet funktioniert, da der zum Sortieren verwendete Schlüssel (y, x) ist. , nicht nur y. Sie sollten stattdessen [x für (y, x) in sortiert (zip (Y, X), Schlüssel = Lambda-Paar: Paar [0])]
gms7777
1
gute Lösung! Aber es sollte sein: Die Liste ist nach dem ersten Element der Paare geordnet, und das Verständnis extrahiert das 'zweite' Element der Paare.
MasterControlProgram
Diese Lösung ist schlecht, wenn es um Lagerung geht. Wann immer möglich, wird eine In-Place-Sortierung bevorzugt.
Hatefiend
107

Zippen Sie die beiden Listen zusammen, sortieren Sie sie und nehmen Sie die gewünschten Teile:

>>> yx = zip(Y, X)
>>> yx
[(0, 'a'), (1, 'b'), (1, 'c'), (0, 'd'), (1, 'e'), (2, 'f'), (2, 'g'), (0, 'h'), (1, 'i')]
>>> yx.sort()
>>> yx
[(0, 'a'), (0, 'd'), (0, 'h'), (1, 'b'), (1, 'c'), (1, 'e'), (1, 'i'), (2, 'f'), (2, 'g')]
>>> x_sorted = [x for y, x in yx]
>>> x_sorted
['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Kombinieren Sie diese, um Folgendes zu erhalten:

[x for y, x in sorted(zip(Y, X))]
Ned Batchelder
quelle
1
Dies ist in Ordnung, wenn Xes sich um eine Liste handelt str, aber seien Sie vorsichtig, wenn es eine Möglichkeit <gibt, die für einige Artikelpaare in nicht definiert ist X, z. B. - wenn einige davon warenNone
John La Rooy
1
Wenn wir versuchen, ein Zip-Objekt zu sortieren, AttributeError: 'zip' object has no attribute 'sort'bekomme ich das ab sofort.
Ash Upadhyay
2
Sie verwenden Python 3. In Python 2 hat zip eine Liste erstellt. Jetzt wird ein iterierbares Objekt erstellt. sorted(zip(...))sollte noch funktionieren, oder: them = list(zip(...)); them.sort()
Ned Batchelder
77

Wenn es Ihnen nichts ausmacht, Numpy-Arrays zu verwenden (oder tatsächlich bereits mit Numpy-Arrays zu tun haben ...), ist hier eine weitere gute Lösung:

people = ['Jim', 'Pam', 'Micheal', 'Dwight']
ages = [27, 25, 4, 9]

import numpy
people = numpy.array(people)
ages = numpy.array(ages)
inds = ages.argsort()
sortedPeople = people[inds]

Ich habe es hier gefunden: http://scienceoss.com/sort-one-list-by-another-list/

Tom
quelle
1
Für größere Arrays / Vektoren ist diese Lösung mit Numpy von Vorteil!
MasterControlProgram
1
Wenn es sich bereits um numpy Arrays handelt, ist dies einfach sortedArray1= array1[array2.argsort()]. Dies macht es auch einfach, mehrere Listen nach einer bestimmten Spalte eines 2D-Arrays sortedArray1= array1[array2[:,2].argsort()]zu sortieren : z. B. Array1 (das mehrere Spalten haben kann) nach den Werten in der dritten Spalte von Array2 zu sortieren.
Aaron Bramson
40

Die naheliegendste Lösung für mich ist die Verwendung des keySchlüsselworts arg.

>>> X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
>>> Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]
>>> keydict = dict(zip(X, Y))
>>> X.sort(key=keydict.get)
>>> X
['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Beachten Sie, dass Sie dies zu einem Einzeiler kürzen können, wenn Sie Folgendes möchten:

>>> X.sort(key=dict(zip(X, Y)).get)
senderle
quelle
2
Erfordert dies, dass die Werte in X eindeutig sind?
Jack Peng
15

Ich bin tatsächlich hierher gekommen, um eine Liste nach einer Liste zu sortieren, in der die Werte übereinstimmen.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']
nackjicholson
quelle
1
Ist das performant?
AFP_555
Keine Ahnung. Berichten Sie zurück, was Sie finden.
Nackjicholson
1
Das ist eine schlechte Idee. indexführt eine O (N) -Suche list_adurch, die zu einer O(N² log N)Sortierung führt.
Richard
Danke, tu das nicht, wenn es auf Leistung ankommt!
nackjicholson
15

more_itertools hat ein Werkzeug zum parallelen Sortieren von Iterables:

Gegeben

from more_itertools import sort_together


X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Demo

sort_together([Y, X])[1]
# ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')
Pylang
quelle
13

Ich mag eine Liste sortierter Indizes. Auf diese Weise kann ich jede Liste in derselben Reihenfolge wie die Quellliste sortieren. Sobald Sie eine Liste mit sortierten Indizes haben, reicht ein einfaches Listenverständnis aus:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

sorted_y_idx_list = sorted(range(len(Y)),key=lambda x:Y[x])
Xs = [X[i] for i in sorted_y_idx_list ]

print( "Xs:", Xs )
# prints: Xs: ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Beachten Sie, dass die sortierte Indexliste auch mit abgerufen werden kann numpy.argsort().

1-ijk
quelle
12

Eine weitere Alternative, bei der mehrere Antworten kombiniert werden.

zip(*sorted(zip(Y,X)))[1]

Um für Python3 zu arbeiten:

list(zip(*sorted(zip(B,A))))[1]
TMC
quelle
7

zip, sortiere nach der zweiten Spalte, gib die erste Spalte zurück.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]
Riza
quelle
Hinweis: Der Schlüssel = operator.itemgetter (1) löst das doppelte Problem
Keith
zip ist nicht abonnierbar ... Sie müssen tatsächlich verwendenlist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
raphael
@ Keith welches doppelte Problem?
Josh
Wenn es mehr als eine Übereinstimmung gibt, erhält sie die erste
Keith
3

Ein schneller Einzeiler.

list_a = [5,4,3,2,1]
list_b = [1,1.5,1.75,2,3,3.5,3.75,4,5]

Angenommen, Sie möchten, dass Liste a mit Liste b übereinstimmt. B.

orderedList =  sorted(list_a, key=lambda x: list_b.index(x))

Dies ist hilfreich, wenn Sie eine kleinere Liste mit größeren Werten bestellen müssen. Angenommen, die größere Liste enthält alle Werte in der kleineren Liste, kann dies durchgeführt werden.

Evan Lalo
quelle
Dies löst die Frage des OP nicht. Haben Sie es mit den Beispiellisten Xund versucht Y?
Aryeh Leib Taurog
Das ist eine schlechte Idee. indexführt eine O (N) -Suche list_bdurch, die zu einer O(N² log N)Sortierung führt.
Richard
1

Sie können eine erstellen pandas Series, indem Sie die Primärliste als dataund die andere Liste als verwenden indexund dann einfach nach dem Index sortieren:

import pandas as pd
pd.Series(data=X,index=Y).sort_index().tolist()

Ausgabe:

['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']
Binyamin Even
quelle
1

Hier ist die Antwort von Whatangs, wenn Sie beide sortierten Listen (python3) erhalten möchten.

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Zx, Zy = zip(*[(x, y) for x, y in sorted(zip(Y, X))])

print(list(Zx))  # [0, 0, 0, 1, 1, 1, 1, 2, 2]
print(list(Zy))  # ['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Denken Sie daran, Zx und Zy sind Tupel. Ich wandere auch, wenn es einen besseren Weg gibt, das zu tun.

Warnung: Wenn Sie es mit leeren Listen ausführen, stürzt es ab.

Iraklis Moutidis
quelle
1

Ich habe eine allgemeinere Funktion erstellt, die mehr als zwei Listen basierend auf einer anderen sortiert, inspiriert von @ Whatangs Antwort.

def parallel_sort(*lists):
    """
    Sorts the given lists, based on the first one.
    :param lists: lists to be sorted

    :return: a tuple containing the sorted lists
    """

    # Create the initially empty lists to later store the sorted items
    sorted_lists = tuple([] for _ in range(len(lists)))

    # Unpack the lists, sort them, zip them and iterate over them
    for t in sorted(zip(*lists)):
        # list items are now sorted based on the first list
        for i, item in enumerate(t):    # for each item...
            sorted_lists[i].append(item)  # ...store it in the appropriate list

    return sorted_lists
pgmank
quelle
0
list1 = ['a','b','c','d','e','f','g','h','i']
list2 = [0,1,1,0,1,2,2,0,1]

output=[]
cur_loclist = []

Um eindeutige Werte in zu erhalten list2

list_set = set(list2)

So finden Sie die Position des Index in list2

list_str = ''.join(str(s) for s in list2)

Die Position des Index in list2wird mit verfolgtcur_loclist

[0, 3, 7, 1, 2, 4, 8, 5, 6]

for i in list_set:
cur_loc = list_str.find(str(i))

while cur_loc >= 0:
    cur_loclist.append(cur_loc)
    cur_loc = list_str.find(str(i),cur_loc+1)

print(cur_loclist)

for i in range(0,len(cur_loclist)):
output.append(list1[cur_loclist[i]])
print(output)
VANI
quelle
0

Dies ist eine alte Frage, aber einige der Antworten, die ich gepostet sehe, funktionieren nicht wirklich, weil sie zipnicht skriptfähig sind. Andere Antworten störten nichtimport operator und bieten hier weitere Informationen zu diesem Modul und seinen Vorteilen.

Es gibt mindestens zwei gute Redewendungen für dieses Problem. Beginnend mit der von Ihnen angegebenen Beispieleingabe:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,   0,   1,   2,   2,   0,   1 ]

Verwenden der Redewendung " Dekorieren-Sortieren-Nichtdekorieren "

Dies ist auch als Schwartzian_transform nach R. Schwartz bekannt, der dieses Muster in den 90er Jahren in Perl populär machte:

# Zip (decorate), sort and unzip (undecorate).
# Converting to list to script the output and extract X
list(zip(*(sorted(zip(Y,X)))))[1]                                                                                                                       
# Results in: ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')

Beachten Sie, dass in diesem Fall Yund Xlexikographisch sortiert und verglichen werden. Das heißt, die ersten Elemente (von Y) werden verglichen; und wenn sie gleich sind, werden die zweiten Elemente (von X) verglichen und so weiter. Dies kann zu Instabilitäten führen Ausgaben führen, es sei denn, Sie geben die ursprünglichen Listenindizes für die lexikografische Reihenfolge an, um Duplikate in ihrer ursprünglichen Reihenfolge zu halten.

Unter Verwendung des operatorModuls

Auf diese Weise können Sie direkter steuern, wie die Eingabe sortiert wird, sodass Sie die Sortierstabilität erhalten, indem Sie einfach den spezifischen Schlüssel angeben, nach dem sortiert werden soll. Weitere Beispiele finden Sie hier .

import operator    

# Sort by Y (1) and extract X [0]
list(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]                                                                                                 
# Results in: ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')
Amelio Vazquez-Reina
quelle