Come ordinare due elenchi (che si fanno riferimento) nello stesso identico modo


139

Supponiamo di avere due elenchi:

list1 = [3, 2, 4, 1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

Se corro list1.sort(), lo ordinerà [1,1,2,3,4]ma c'è anche un modo per list2sincronizzarsi (quindi posso dire che l'articolo 4appartiene 'three')? Pertanto, l'output previsto sarebbe:

list1 = [1, 1, 2, 3, 4]
list2 = ['one', 'one2', 'two', 'three', 'four']

Il mio problema è che ho un programma piuttosto complesso che funziona bene con le liste ma ho bisogno di iniziare a fare riferimento ad alcuni dati. So che questa è una situazione perfetta per i dizionari, ma sto cercando di evitare i dizionari nella mia elaborazione perché ho bisogno di ordinare i valori chiave (se devo usare i dizionari, so come usarli).

Fondamentalmente la natura di questo programma è, i dati arrivano in un ordine casuale (come sopra), ho bisogno di ordinarli, elaborarli e quindi inviare i risultati (l'ordine non ha importanza ma gli utenti devono sapere a quale risultato appartiene a quale chiave). Ho pensato di metterlo prima in un dizionario, quindi di elencarne uno ma non avrei modo di differenziare gli articoli con lo stesso valore se l'ordine non fosse mantenuto (potrebbe avere un impatto quando si comunicano i risultati agli utenti). Quindi idealmente, una volta che avrò le liste, preferirei trovare un modo per ordinare entrambe le liste. È possibile?


Dovrei sottolineare che le tue variabili in list2 non puntano agli in in list1. Ad esempio, se si modifica un valore come list1 [0] = 9 e si osserva list2, list2 [0] sarà comunque 3. Con numeri interi in Python, non utilizza il riferimento / puntatore, ma si copia il valore. Sarebbe stato meglio andare list2 = list1 [:]
robert king il

Risposte:


242

Un approccio classico a questo problema è l'uso del linguaggio "decorare, ordinare, non decorato", che è particolarmente semplice usando la funzione integrata di Python zip:

>>> list1 = [3,2,4,1, 1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> list1, list2 = zip(*sorted(zip(list1, list2)))
>>> list1
(1, 1, 2, 3, 4)
>>> list2 
('one', 'one2', 'two', 'three', 'four')

Questi ovviamente non sono più elenchi, ma è facilmente risolvibile, se è importante:

>>> list1, list2 = (list(t) for t in zip(*sorted(zip(list1, list2))))
>>> list1
[1, 1, 2, 3, 4]
>>> list2
['one', 'one2', 'two', 'three', 'four']

Vale la pena notare che quanto sopra può sacrificare la velocità per la terseness; la versione sul posto, che occupa 3 righe, è un po 'più veloce sulla mia macchina per piccoli elenchi:

>>> %timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 3.3 us per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best of 3: 2.84 us per loop

D'altra parte, per elenchi più grandi, la versione a una riga potrebbe essere più veloce:

>>> %timeit zip(*sorted(zip(list1, list2)))
100 loops, best of 3: 8.09 ms per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100 loops, best of 3: 8.51 ms per loop

Come sottolinea Quantum7, il suggerimento di JSF è ancora un po 'più veloce, ma probabilmente sarà sempre e solo un po' più veloce, perché Python utilizza lo stesso linguaggio DSU internamente per tutti i tipi di chiavi. Sta succedendo un po 'più vicino al metallo nudo. (Questo dimostra quanto siano ottimizzate le ziproutine!)

Penso che la zip approccio basato sia più flessibile ed sia un po 'più leggibile, quindi lo preferisco.


6
cosa rappresenta l'asterisco nella terza riga?
Jeffrey

8
Per approfondire quanto sopra, l' *operatore fa disimballaggio ,
mittente

1
Il paradigma dell'indice / mappa ordinato suggerito da JF Sebastian è circa il 10% più veloce di una delle due soluzioni zip per me (usando elenchi di 10000 ints casuali):% timeit index = range (len (l1)); index.sort (key = l1 .__ getitem__); mappa (l1 .__ getitem__, indice); mappa (l2 .__ getitem__, indice) 100 loop, meglio di 3: 8,04 ms per loop (vs 9,17 ms, 9,07 ms per i tempi di senderle)
Quantum7

1
Il primo e il secondo zip in list1, list2 = zip (* ordinati (zip (list1, list2))) fanno cose così diverse. Il * fa la differenza.
ashu,

1
@ashu, in un certo senso, sì! Ma in un altro senso, non sono affatto diversi. zip(*x)ha la proprietà interessante che è il suo contrario: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lrestituisce True. È effettivamente un operatore di trasposizione. zip()di per sé è solo lo stesso operatore, ma presuppone che tu abbia decompresso manualmente la sequenza di input.
mittente

30

Puoi ordinare gli indici usando i valori come chiavi:

indexes = range(len(list1))
indexes.sort(key=list1.__getitem__)

Per ottenere elenchi ordinati dati indici ordinati:

sorted_list1 = map(list1.__getitem__, indexes)
sorted_list2 = map(list2.__getitem__, indexes)

Nel tuo caso non dovresti avere list1, list2ma piuttosto un singolo elenco di coppie:

data = [(3, 'three'), (2, 'two'), (4, 'four'), (1, 'one'), (1, 'one2')]

È facile da creare; è facile ordinare in Python:

data.sort() # sort using a pair as a key

Ordina solo per il primo valore:

data.sort(key=lambda pair: pair[0])

La cosa interessante di questo è che posso tenere gli indici intorno e ordinare altre cose in seguito, nel caso in cui list1 sia una coordinata importante che influisce su molti altri array.
EL_DON

3
indexes = list (range (len (list1))) per python 3
DonQuiKong

@DonQuiKong devi anche guardarti list() intorno map()se vuoi usare questo codice in Python 3.
jfs

Oppure, invece di sorted_list1 = list(map(list1.__getitem__, indexes))uno si potrebbe fare sorted_list1 = [list1[i] for i in indexes].
Nathan il

20

Ho usato la risposta data da Senderle per molto tempo fino a quando ho scoperto np.argsort. Ecco come funziona.

# idx works on np.array and not lists.
list1 = np.array([3,2,4,1])
list2 = np.array(["three","two","four","one"])
idx   = np.argsort(list1)

list1 = np.array(list1)[idx]
list2 = np.array(list2)[idx]

Trovo questa soluzione più intuitiva e funziona davvero bene. La prestazione:

def sorting(l1, l2):
    # l1 and l2 has to be numpy arrays
    idx = np.argsort(l1)
    return l1[idx], l2[idx]

# list1 and list2 are np.arrays here...
%timeit sorting(list1, list2)
100000 loops, best of 3: 3.53 us per loop

# This works best when the lists are NOT np.array
%timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 2.41 us per loop

# 0.01us better for np.array (I think this is negligible)
%timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best for 3 loops: 1.96 us per loop

Anche se np.argsortnon è il più veloce, lo trovo più facile da usare.


1
TypeError: only integer arrays with one element can be converted to an indexViene visualizzato un errore durante l'esempio: (Python 2.7.6, numpy 1.8.2). Per risolverlo, list1 e list2 devono essere dichiarati come array numpy.
BenB,

Grazie. Non è questo ciò che scrivo nel commento nella funzione? Ad ogni modo, penso sia sciocco che np.argsortnon cerchi di convertirsi in un np.arrayinternamente.
Daniel Thaagaard Andreasen,

Mi riferivo al primo frammento di codice poiché non funziona come scritto :)
BenB

L'ho corretto convertendo gli elenchi quando sono assegnati ad array intorpiditi. Grazie per il commento :)
Daniel Thaagaard Andreasen,

Ora vengono convertiti due volte in array Numpy;)
BenB l'

13

Trasformata di Schwartz . L'ordinamento Python integrato è stabile, quindi i due 1non causano problemi.

>>> l1 = [3, 2, 4, 1, 1]
>>> l2 = ['three', 'two', 'four', 'one', 'second one']
>>> zip(*sorted(zip(l1, l2)))
[(1, 1, 2, 3, 4), ('one', 'second one', 'two', 'three', 'four')]

2
Tuttavia, se trovi che devi farlo, dovresti riconsiderare fortemente di avere i due elenchi di dati "paralleli", invece di mantenere un elenco di 2 tuple (coppie) ... o forse persino di creare effettivamente una classe .
Karl Knechtel,

3

Che dire:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

sortedRes = sorted(zip(list1, list2), key=lambda x: x[0]) # use 0 or 1 depending on what you want to sort
>>> [(1, 'one'), (1, 'one2'), (2, 'two'), (3, 'three'), (4, 'four')]

2

È possibile utilizzare le funzioni zip()e sort()per eseguire ciò:

Python 2.6.5 (r265:79063, Jun 12 2010, 17:07:01)
[GCC 4.3.4 20090804 (release) 1] on cygwin
>>> list1 = [3,2,4,1,1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> zipped = zip(list1, list2)
>>> zipped.sort()
>>> slist1 = [i for (i, s) in zipped]
>>> slist1
[1, 1, 2, 3, 4]
>>> slist2 = [s for (i, s) in zipped]
>>> slist2
['one', 'one2', 'two', 'three', 'four']

Spero che questo ti aiuti


2

È possibile utilizzare l'argomento chiave nel metodo sort () a meno che non si abbiano due stessi valori in list2.

Il codice è indicato di seguito:

sorted(list2, key = lambda x: list1[list2.index(x)]) 

Ordina list2 in base ai valori corrispondenti in list1, ma assicurati che durante l'utilizzo di questo, nessun due valori in list2 valuti come uguali perché la funzione list.index () fornisce il primo valore


ordinato è un po 'lento in alcune condizioni sebbene funzioni.
tyan,

2

Un modo è quello di tracciare dove va ogni indice ordinando l'identità [0,1,2, .. n]

Funziona con qualsiasi numero di liste.

Quindi spostare ciascun elemento nella sua posizione. È meglio usare le giunzioni.

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

index = list(range(len(list1)))
print(index)
'[0, 1, 2, 3, 4]'

index.sort(key = list1.__getitem__)
print(index)
'[3, 4, 1, 0, 2]'

list1[:] = [list1[i] for i in index]
list2[:] = [list2[i] for i in index]

print(list1)
print(list2)
'[1, 1, 2, 3, 4]'
"['one', 'one2', 'two', 'three', 'four']"

Nota che avremmo potuto iterare gli elenchi senza nemmeno ordinarli:

list1_iter = (list1[i] for i in index)

1

Se si utilizza numpy è possibile utilizzare np.argsortper ottenere gli indici ordinati e applicare tali indici all'elenco. Funziona con qualsiasi numero di elenco che desideri ordinare.

import numpy as np

arr1 = np.array([4,3,1,32,21])
arr2 = arr1 * 10
sorted_idxs = np.argsort(arr1)

print(sorted_idxs)
>>> array([2, 1, 0, 4, 3])

print(arr1[sorted_idxs])
>>> array([ 1,  3,  4, 21, 32])

print(arr2[sorted_idxs])
>>> array([ 10,  30,  40, 210, 320])

0

una soluzione algoritmica:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']


lis = [(list1[i], list2[i]) for i in range(len(list1))]
list1.sort()
list2 = [x[1] for i in range(len(list1)) for x in lis if x[0] == i]

Uscite: -> velocità di uscita: 0.2s

>>>list1
>>>[1, 1, 2, 3, 4]
>>>list2
>>>['one', 'one2', 'two', 'three', 'four']

0

Un altro approccio per mantenere l'ordine di un elenco di stringhe durante l'ordinamento rispetto a un altro elenco è il seguente:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

# sort on list1 while retaining order of string list
sorted_list1 = [y for _,y in sorted(zip(list1,list2),key=lambda x: x[0])]
sorted_list2 = sorted(list1)

print(sorted_list1)
print(sorted_list2)

produzione

['one', 'one2', 'two', 'three', 'four']
[1, 1, 2, 3, 4]

0

Vorrei espandere la risposta di jfs aperta , che ha funzionato perfettamente per il mio problema: ordinare due elenchi per un terzo elenco decorato :

Possiamo creare la nostra lista decorata in qualsiasi modo, ma in questo caso la creeremo dagli elementi di una delle due liste originali, che vogliamo ordinare:

# say we have the following list and we want to sort both by the algorithms name 
# (if we were to sort by the string_list, it would sort by the numerical 
# value in the strings)
string_list = ["0.123 Algo. XYZ", "0.345 Algo. BCD", "0.987 Algo. ABC"]
dict_list = [{"dict_xyz": "XYZ"}, {"dict_bcd": "BCD"}, {"dict_abc": "ABC"}]

# thus we need to create the decorator list, which we can now use to sort
decorated = [text[6:] for text in string_list]  
# decorated list to sort
>>> decorated
['Algo. XYZ', 'Algo. BCD', 'Algo. ABC']

Ora possiamo applicare la soluzione di jfs per ordinare i nostri due elenchi per terzo

# create and sort the list of indices
sorted_indices = list(range(len(string_list)))
sorted_indices.sort(key=decorated.__getitem__)

# map sorted indices to the two, original lists
sorted_stringList = list(map(string_list.__getitem__, sorted_indices))
sorted_dictList = list(map(dict_list.__getitem__, sorted_indices))

# output
>>> sorted_stringList
['0.987 Algo. ABC', '0.345 Algo. BCD', '0.123 Algo. XYZ']
>>> sorted_dictList
[{'dict_abc': 'ABC'}, {'dict_bcd': 'BCD'}, {'dict_xyz': 'XYZ'}]

Modifica: Ehi ragazzi, ho scritto un post a blocchi su questo, date un'occhiata se ne avete voglia :) 🐍🐍🐍


-1
newsource=[];newtarget=[]
for valueT in targetFiles:
    for valueS in sourceFiles:
            l1=len(valueS);l2=len(valueT);
            j=0
            while (j< l1):
                    if (str(valueT) == valueS[j:l1]) :
                            newsource.append(valueS)
                            newtarget.append(valueT)
                    j+=1

2
un paio di righe di spiegazione sarebbero utili
saiedmomen il

@saiedmomen L'ho pubblicato in riferimento a stackoverflow.com/questions/53829160/… Qui la stringa di destinazione viene cercata sulla stringa di origine.
user10340258
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.