Elenco di ordinamento basato sui valori di un altro elenco?


370

Ho un elenco di stringhe come questa:

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

Qual è il modo più breve per ordinare X usando i valori da Y per ottenere il seguente output?

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

L'ordine degli elementi che hanno la stessa "chiave" non ha importanza. Posso ricorrere all'uso di forcostrutti ma sono curioso di sapere se esiste un modo più breve. Eventuali suggerimenti?


La risposta di riza potrebbe essere utile quando si tracciano i dati, poiché zip (* ordinato (zip (X, Y), chiave = coppia lambda: coppia [0])) restituisce sia la X che la Y ordinate con i valori di X.
jojo

Risposte:


479

Codice più breve

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

Esempio:

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"]

Parlando in generale

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

Ha spiegato:

  1. ziple due lists.
  2. creare un nuovo, ordinato in listbase zipall'uso sorted().
  3. usando una comprensione dell'elenco estrarre i primi elementi di ciascuna coppia dall'ordinato, compresso list.

Per ulteriori informazioni su come impostare \ utilizzare il keyparametro e la sortedfunzione in generale, dai un'occhiata a questo .



117
Questo è corretto, ma aggiungerò la nota che se stai cercando di ordinare più array dallo stesso array, questo non funzionerà necessariamente come previsto, poiché la chiave che viene utilizzata per ordinare è (y, x) , non solo y. Dovresti invece usare [x per (y, x) in ordine (zip (Y, X), chiave = coppia lambda: coppia [0])]
gms7777,

1
buona soluzione! Ma dovrebbe essere: l'elenco è ordinato per quanto riguarda il primo elemento delle coppie e la comprensione estrae il "secondo" elemento delle coppie.
MasterControlProgram

Questa soluzione è scadente quando si tratta di archiviazione. Un ordinamento sul posto è preferito quando possibile.
Hatefiend,

107

Comprimi le due liste insieme, ordinale, quindi prendi le parti che desideri:

>>> 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']

Combina questi insieme per ottenere:

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

1
Questo va bene se Xè un elenco di str, ma fai attenzione se esiste una possibilità che <non è definita per alcune coppie di elementi X, ad esempio - se alcuni di essi fosseroNone
John La Rooy,

1
Quando proviamo a utilizzare l'ordinamento su un oggetto zip, AttributeError: 'zip' object has no attribute 'sort'è quello che sto ottenendo al momento.
Ash Upadhyay,

2
Stai usando Python 3. In Python 2, zip ha prodotto un elenco. Ora produce un oggetto iterabile. sorted(zip(...))dovrebbe ancora funzionare, oppure: them = list(zip(...)); them.sort()
Ned Batchelder il

77

Inoltre, se non ti dispiace usare array numpy (o in realtà hai già a che fare con array numpy ...), ecco un'altra bella soluzione:

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]

L'ho trovato qui: http://scienceoss.com/sort-one-list-by-another-list/


1
Per array / vettori più grandi, questa soluzione con intorpidimento è vantaggiosa!
MasterControlProgram

1
Se sono già array intorpiditi, allora è semplicemente sortedArray1= array1[array2.argsort()]. Inoltre, è facile ordinare più elenchi in base a una particolare colonna di un array 2D: ad esempio, sortedArray1= array1[array2[:,2].argsort()]ordinare array1 (che può avere più colonne) in base ai valori nella terza colonna di array2.
Aaron Bramson,

40

La soluzione più ovvia per me è usare la keyparola chiave 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']

Tieni presente che puoi accorciarlo a una linea se ti interessa:

>>> X.sort(key=dict(zip(X, Y)).get)

2
Ciò richiede che i valori in X siano unqiue?
Jack Peng

15

In realtà sono venuto qui cercando di ordinare un elenco in base a un elenco in cui i valori corrispondevano.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']

1
Questo è performante?
AFP_555,

Nessun indizio. Segnala ciò che trovi.
nackjicholson,

1
Questa è una cattiva idea. indexeseguirà una ricerca O (N) su list_arisultante in un O(N² log N)ordinamento.
Richard

Grazie, non farlo quando le prestazioni contano!
nackjicholson,

15

more_itertools ha uno strumento per ordinare in parallelo gli iterabili:

Dato

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]

dimostrazione

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

13

Mi piace avere un elenco di indici ordinati. In questo modo, posso ordinare qualsiasi elenco nello stesso ordine dell'elenco delle fonti. Una volta che hai un elenco di indici ordinati, una semplice comprensione dell'elenco farà il trucco:

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"]

Si noti che l'elenco di indici ordinati può anche essere ottenuto utilizzando numpy.argsort().


12

Un'altra alternativa, che combina diverse risposte.

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

Per lavorare con python3:

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

7

zip, ordina per seconda colonna, restituisce la prima colonna.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]

Nota: key = operator.itemgetter (1) risolve il problema duplicato
Keith

zip non è sottoscrivibile ... devi effettivamente usarlolist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
raphael

@Keith quale problema duplicato?
Josh,

Se c'è più di una corrispondenza, si ottiene la prima
Keith

3

Una veloce fodera.

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

Dire che si desidera che l'elenco a corrisponda all'elenco b.

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

Ciò è utile quando è necessario ordinare un elenco più piccolo con valori più grandi. Supponendo che l'elenco più grande contenga tutti i valori nell'elenco più piccolo, è possibile farlo.


Ciò non risolve la domanda del PO. L'hai provato con gli elenchi di esempio Xe Y?
Aryeh Leib Taurog,

Questa è una cattiva idea. indexeseguirà una ricerca O (N) su list_brisultante in un O(N² log N)ordinamento.
Richard

1

È possibile creare un pandas Series, utilizzando l'elenco primario come datae l'altro elenco come index, quindi ordinare semplicemente per indice:

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

produzione:

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

1

Ecco la risposta di Whatangs se vuoi ottenere entrambi gli elenchi ordinati (python3).

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']

Ricorda solo che Zx e Zy sono tuple. Sto anche vagando se c'è un modo migliore per farlo.

Avvertenza: se lo esegui con elenchi vuoti si arresta in modo anomalo.


1

Ho creato una funzione più generale, che ordina più di due elenchi in base a un altro, ispirato dalla risposta di @ Whatang.

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

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

output=[]
cur_loclist = []

Per ottenere valori univoci presenti in list2

list_set = set(list2)

Per trovare la loc dell'indice in list2

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

La posizione dell'indice in list2viene tracciata utilizzandocur_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)

0

Questa è una vecchia domanda, ma alcune delle risposte che vedo pubblicate in realtà non funzionano perché zipnon sono programmabili. Altre risposte non si sono preoccupate di import operatorfornire ulteriori informazioni su questo modulo e sui suoi vantaggi qui.

Ci sono almeno due buoni modi di dire per questo problema. A partire dall'input di esempio fornito:

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

Usando il linguaggio " Decorate-Sort-Undecorate "

Questo è anche noto come Schwartzian_transform dopo R. Schwartz che ha reso popolare questo modello in Perl negli anni '90:

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

Si noti che in questo caso Ye Xsono ordinati e confrontati lessicograficamente. Cioè, i primi elementi (da Y) vengono confrontati; e se sono uguali, Xvengono confrontati i secondi elementi (da ) e così via. Ciò può creare output instabili a meno che non si includano gli indici dell'elenco originale per l'ordine lessicografico per mantenere i duplicati nel loro ordine originale.

Utilizzando il operatormodulo

Questo ti dà un controllo più diretto su come ordinare l'input, in modo da poter ottenere la stabilità dell'ordinamento semplicemente dichiarando la chiave specifica per ordinare. Vedi altri esempi qui .

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')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.