Cómo ordenar dos listas (que se refieren entre sí) exactamente de la misma manera


139

Digamos que tengo dos listas:

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

Si ejecuto list1.sort(), lo clasificará, [1,1,2,3,4]pero ¿hay alguna forma de list2sincronizar también (por lo que puedo decir que el elemento 4pertenece 'three')? Entonces, el resultado esperado sería:

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

Mi problema es que tengo un programa bastante complejo que funciona bien con las listas, pero necesito comenzar a hacer referencia a algunos datos. Sé que esta es una situación perfecta para los diccionarios, pero estoy tratando de evitar los diccionarios en mi procesamiento porque necesito ordenar los valores clave (si debo usar diccionarios, sé cómo usarlos).

Básicamente, la naturaleza de este programa es que los datos vienen en un orden aleatorio (como el anterior), necesito ordenarlos, procesarlos y luego enviar los resultados (el orden no importa, pero los usuarios necesitan saber qué resultado pertenece a qué llave). Pensé en ponerlo primero en un diccionario, luego ordenar la lista uno, pero no tendría forma de diferenciar los elementos con el mismo valor si no se mantiene el orden (puede tener un impacto al comunicar los resultados a los usuarios). Entonces, idealmente, una vez que obtenga las listas, preferiría encontrar una manera de ordenar ambas listas juntas. es posible?


Debo señalar que sus variables en list2 no apuntan a las entradas en list1. Por ejemplo, si cambia un valor como list1 [0] = 9 y mira list2, list2 [0] seguirá siendo 3. Con enteros en python, no usa la referencia / puntero, copia el valor. Hubiera sido mejor ir lista2 = lista1 [:]
robert king

Respuestas:


242

Un enfoque clásico para este problema es usar el modismo "decorar, ordenar, decorar", que es especialmente simple usando la función incorporada de 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')

Estas, por supuesto, ya no son listas, pero eso se puede remediar fácilmente, si es 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 señalar que lo anterior puede sacrificar la velocidad por la brevedad; la versión in situ, que ocupa 3 líneas, es un poco más rápida en mi máquina para listas pequeñas:

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

Por otro lado, para listas más grandes, la versión de una línea podría ser más rápida:

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

Como Quantum7 señala, la sugerencia de JSF es un poco más rápida aún, pero probablemente solo sea un poco más rápida, porque Python usa el mismo idioma de DSU internamente para todo tipo de claves. Simplemente está sucediendo un poco más cerca del metal desnudo. (¡Esto muestra cuán optimizadas están las ziprutinas!)

Creo que el zipenfoque basado en la base es más flexible y un poco más legible, así que lo prefiero.


66
¿Qué representa el asterisco en la tercera línea?
Jeffrey

8
Para dar más detalles sobre lo anterior, el *operador hace el desempaque de argumentos ,
remitente

1
El paradigma de índice / mapa ordenado sugerido por JF Sebastian es aproximadamente un 10% más rápido que cualquier solución zip para mí (usando listas de 10000 entradas aleatorias):% timeit index = range (len (l1)); index.sort (key = l1 .__ getitem__); mapa (l1 .__ getitem__, index); mapa (l2 .__ getitem__, index) 100 bucles, mejor de 3: 8,04 ms por bucle (frente a 9,17 ms, 9,07 ms para los tiempos de los remitentes)
Quantum7

1
El primer y segundo zip en list1, list2 = zip (* sorted (zip (list1, list2))) hacen cosas tan diferentes. El * hace toda la diferencia.
ashu

1
@ashu, en cierto sentido, ¡sí! Pero en otro sentido, no son para nada diferentes. zip(*x)tiene la interesante propiedad de que es su propio inverso: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lretornos True. Es efectivamente un operador de transposición. zip()por sí solo es el mismo operador, pero supone que ha desempaquetado la secuencia de entrada manualmente.
senderle

30

Puede ordenar los índices utilizando valores como claves:

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

Para obtener listas ordenadas dados índices ordenados:

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

En su caso, no debería tener list1, list2sino una sola lista de pares:

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

Es fácil de crear; es fácil de ordenar en Python:

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

Ordenar solo por el primer valor:

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

Lo bueno de esto es que puedo mantener los índices y ordenar otras cosas más tarde, en el caso de que list1 es una coordenada importante que afecta a varias otras matrices.
EL_DON

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

@DonQuiKong también es necesario para list() todo map(), si desea utilizar este código en Python 3.
JFS

O, en lugar de sorted_list1 = list(map(list1.__getitem__, indexes))uno, podría hacerlo sorted_list1 = [list1[i] for i in indexes].
Nathan

20

He usado la respuesta dada por senderle durante mucho tiempo hasta que descubrí np.argsort. Así es como funciona.

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

Encuentro esta solución más intuitiva y funciona muy bien. El rendimiento:

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

Aunque np.argsortno es el más rápido, me resulta más fácil de usar.


1
Recibo un error al ejecutar su ejemplo: TypeError: only integer arrays with one element can be converted to an index(Python 2.7.6, numpy 1.8.2). Para solucionarlo, list1 y list2 deben declararse como matrices numpy.
BenB

Gracias. ¿No es esto lo que escribo en el comentario en la función? De todos modos, creo que es una tontería que np.argsortno intentes convertir a un np.arrayinterno.
Daniel Thaagaard Andreasen

Me refería al primer fragmento de código ya que no se ejecuta como está escrito :)
BenB

Lo corregí convirtiendo las listas cuando se asignan a matrices numpy. Gracias por el comentario :)
Daniel Thaagaard Andreasen

Ahora se convierten a matrices Numpy dos veces;)
BenB

13

Transformación de Schwartz . La clasificación de Python incorporada es estable, por lo que los dos 1s no causan ningún problema.

>>> 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
Sin embargo, si encuentra que necesita hacer esto, debería volver a considerar tener las dos listas de datos "paralelas", en lugar de mantener una lista de 2 tuplas (pares) ... o tal vez incluso crear una clase .
Karl Knechtel

3

Qué pasa:

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

Puede usar las funciones zip()y sort()para lograr esto:

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

Espero que esto ayude


2

Puede usar el argumento clave en el método sorted () a menos que tenga dos mismos valores en list2.

El código se da a continuación:

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

Ordena list2 según los valores correspondientes en list1, pero asegúrese de que al usar esto, no haya dos valores en list2 que sean iguales porque la función list.index () proporciona el primer valor


ordenado es algo lento en algunas condiciones, aunque funciona.
tyan

2

Una forma es rastrear a dónde va cada índice clasificando la identidad [0,1,2, .. n]

Esto funciona para cualquier cantidad de listas.

Luego mueva cada elemento a su posición. Usar empalmes es lo mejor.

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

Tenga en cuenta que podríamos haber iterado las listas sin siquiera ordenarlas:

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

1

Si está usando numpy, puede usarlo np.argsortpara obtener los índices ordenados y aplicar esos índices a la lista. Esto funciona para cualquier número de lista que desee ordenar.

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 solución algorítmica:

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]

Salidas: -> Velocidad de salida: 0.2s

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

0

Otro enfoque para retener el orden de una lista de cadenas al ordenar en otra lista es el siguiente:

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)

salida

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

0

Me gustaría ampliar la respuesta de jfs abierta , que funcionó muy bien para mi problema: ordenar dos listas por una tercera lista decorada :

Podemos crear nuestra lista decorada de cualquier manera, pero en este caso la crearemos a partir de los elementos de una de las dos listas originales, que queremos ordenar:

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

Ahora podemos aplicar la solución de jfs para ordenar nuestras dos listas por la tercera

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

Editar: Hola chicos, hice una publicación en bloque sobre esto, échale un vistazo si te apetece :) 🐍🐍🐍


-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 par de líneas de explicación serían útiles
saiedmomen

@saiedmomen Lo publiqué en referencia a stackoverflow.com/questions/53829160/… Aquí se busca la cadena de destino sobre la cadena de origen.
user10340258
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.