Dadas las listas paralelas, ¿cómo puedo ordenar una mientras permuto (reorganizo) la otra de la misma manera?

9 minutos de lectura

Avatar de usuario de Lostsoul
Alma perdida

Supongamos que tengo:

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

Vocación list1.sort() lo ordenará, resultando en [1, 1, 2, 3, 4]. Sin embargo, ¿puedo obtener list2 ser reorganizado en sincronía con eso, para obtener un resultado como este?

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

A veces, las personas expresan el problema de manera diferente: dadas dos listas, les gustaría usar una para determinar el orden de clasificación para la otra, es decir, ordenar list2 en el orden descrito por los valores correspondientes en list1. El truco es que esto es equivalente a ordenar los valores “clave” (list1), y luego reorganizar list2 del mismo modo. En otras palabras, exactamente lo que se describe aquí. Sin embargo, algunas respuestas para la otra pregunta descartan las “claves ordenadas”.

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

    – Rob oxidado

    19 de marzo de 2012 a las 3:10

avatar de usuario de senderle
enviarle

Un enfoque clásico para este problema es usar el modismo “decorar, ordenar, desdecorar”, que es especialmente simple usando la función integrada de Python. zip función:

>>> 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 soluciona fácilmente, si importa:

>>> list1, list2 = (list
>>> 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 señala Quantum7, la sugerencia de JSF es un poco más rápida aún, pero probablemente solo será un poco más rápida, porque Python usa el mismo idioma DSU internamente para todas las clasificaciones basadas en claves. Simplemente está sucediendo un poco más cerca del metal desnudo. (Esto muestra lo bien optimizado que está el zip las rutinas son!)

Pienso que el zipEl enfoque basado en es más flexible y es un poco más legible, así que lo prefiero.


Tenga en cuenta que cuando los elementos de list1 son iguales, este enfoque terminará comparando elementos de list2. Si elementos de list2 no admiten la comparación, o no producen un valor booleano cuando se comparan (por ejemplo, si list2 es una lista de arreglos NumPy), esto fallará, y si los elementos de list2 son muy caros de comparar, sería mejor evitar la comparación de todos modos.

En ese caso, puede ordenar los índices como se sugiere en la respuesta de jfs, o puede asignarle a la ordenación una función clave que evite comparar elementos de list2:

result1, result2 = zip(*sorted(zip(list1, list2), key=lambda x: x[0]))

Asimismo, el uso de zip(*...) ya que una transposición falla cuando la entrada está vacía. Si sus entradas pueden estar vacías, tendrá que manejar ese caso por separado.

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

    – Jeffrey

    19 de marzo de 2012 a las 5:25

  • Para profundizar en lo anterior, el * el operador hace desempaque de argumentos,

    – senderle

    19 de marzo de 2012 a las 15:46


  • El paradigma ordenado de índice/mapa sugerido por JF Sebastian es aproximadamente un 10% más rápido que cualquiera de las soluciones zip para mí (usando listas de 10000 enteros aleatorios): %timeit index = range(len(l1)); index.sort(clave=l1.__getitem__); mapa(l1.__getitem__, índice); map(l2.__getitem__, index) 100 bucles, lo mejor de 3: 8,04 ms por bucle (vs 9,17 ms, 9,07 ms para los timits de senderle)

    – Quantum7

    12/08/2013 a las 21:35


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

    – flautista de Hamelín

    13 de enero de 2018 a las 9:48


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

    – senderle

    16 de enero de 2018 a las 14:43


avatar de usuario de jfs
jfs

Puede ordenar índices usando 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 tu caso no debiste list1, list2 sino más bien 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 caso de que list1 sea una coordenada importante que afecte a varias otras matrices.

    – EL_DON

    5 de marzo de 2018 a las 18:35

  • índices = lista (rango (len (lista1))) para python 3

    – DonQuiKong

    2 de noviembre de 2018 a las 9:38

  • @DonQuiKong también necesitas list() alrededor map() si desea utilizar este código en Python 3.

    – jfs

    2 de noviembre de 2018 a las 16:51

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

    – Nathan

    21 de junio de 2020 a las 3:57

Avatar de usuario de Daniel Thaagaard Andreasen
Daniel Thaagaard Andreasen

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]

Esta solución me parece 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

A pesar de np.argsort no es el más rápido, me resulta más fácil de usar.

  • Recibo un error al ejecutar tu 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.

    – Ben B

    7 julio 2015 a las 0:53

  • 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.argsort no intente convertir a un np.array internamente.

    –Daniel Thaagaard Andreasen

    7 julio 2015 a las 12:37

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

    – Ben B

    7 julio 2015 a las 20:50


  • Lo corregí convirtiendo las listas cuando se asignan a matrices numpy. Gracias por el comentario 🙂

    –Daniel Thaagaard Andreasen

    8 de julio de 2015 a las 14:47

  • Ahora se convierten en matrices Numpy dos veces;)

    – Ben B

    08/07/2015 a las 19:32

Avatar de usuario de Karl Knechtel
Carlos Knechtel

Esto se puede hacer usando lo que los programadores de Perl llaman el Transformada Schwartziana, también conocido como el idioma decorar-clasificar-desdecorar. La ordenación integrada de Python es estable, por lo que los dos 1s no causan un 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')]

Puedes usar el zip() y sort() funciones 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

  • ¿Alguien más recibe el error “AttributeError: el objeto ‘zip’ no tiene atributo ‘sort'”? Me pregunto si esta respuesta funciona para versiones anteriores de Python pero no para las actuales.

    – No contradicción

    25 oct 2021 a las 18:32

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

Esto funciona para cualquier número 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)

  • ¿Alguien más recibe el error “AttributeError: el objeto ‘zip’ no tiene atributo ‘sort'”? Me pregunto si esta respuesta funciona para versiones anteriores de Python pero no para las actuales.

    – No contradicción

    25 oct 2021 a las 18:32

Avatar de usuario de Artsiom Rudzenka
Artsiom Rudzenka

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

¿Ha sido útil esta solución?