¿Lista de clasificación basada en valores de otra lista?

370

Tengo una lista de cadenas como esta:

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

¿Cuál es la forma más corta de ordenar X usando valores de Y para obtener el siguiente resultado?

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

El orden de los elementos que tienen la misma "clave" no importa. Puedo recurrir al uso de forconstrucciones, pero tengo curiosidad por saber si hay un camino más corto. ¿Alguna sugerencia?

Leyenda
fuente
La respuesta de riza podría ser útil al trazar datos, ya que zip (* sorted (zip (X, Y), key = lambda pair: pair [0])) devuelve tanto la X ordenada como la Y ordenada con valores de X.
jojo

Respuestas:

479

Código más corto

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

Ejemplo:

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

Generalmente hablando

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

Explicado:

  1. ziplos dos lists.
  2. crear un nuevo, ordenado en listfunción del zipuso sorted().
  3. utilizando una lista de comprensión extraiga los primeros elementos de cada par de los ordenados, comprimidos list.

Para obtener más información sobre cómo configurar \ utilizar el keyparámetro, así como la sortedfunción en general, echar un vistazo a esto .


Whatang
fuente
117
Esto es correcto, pero agregaré la nota de que si está tratando de ordenar múltiples matrices por la misma matriz, esto no necesariamente funcionará como se esperaba, ya que la clave que se utiliza para ordenar es (y, x) , no solo y. En su lugar, debe usar [x para (y, x) en orden (zip (Y, X), clave = par lambda: par [0])]
gms7777
1
¡buena solución! Pero debería ser: la lista está ordenada con respecto al primer elemento de los pares, y la comprensión extrae el 'segundo' elemento de los pares.
MasterControlProgram
Esta solución es pobre cuando se trata de almacenamiento. Se prefiere una ordenación in situ siempre que sea posible.
Hatefiend
107

Comprime las dos listas, clasifícalas y luego toma las partes que quieras:

>>> 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 estos para obtener:

[x for y, x in sorted(zip(Y, X))]
Ned Batchelder
fuente
1
Esto está bien si Xes una lista de str, pero tenga cuidado si hay una posibilidad que <no está definida para algunos pares de elementos en X, por ejemplo, si algunos de ellos lo fueranNone
John La Rooy el
1
Cuando intentamos usar sort sobre un objeto zip, AttributeError: 'zip' object has no attribute 'sort'es lo que obtengo a partir de ahora.
Ash Upadhyay el
2
Está utilizando Python 3. En Python 2, zip produjo una lista. Ahora produce un objeto iterable. sorted(zip(...))todavía debería funcionar, o: them = list(zip(...)); them.sort()
Ned Batchelder
77

Además, si no le importa usar matrices numpy (o de hecho ya está tratando con matrices numpy ...), aquí hay otra buena solución:

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]

Lo encontré aquí: http://scienceoss.com/sort-one-list-by-another-list/

Tom
fuente
1
¡Para matrices / vectores más grandes, esta solución con numpy es beneficiosa!
MasterControlProgram
1
Si ya son matrices numpy, entonces es simplemente sortedArray1= array1[array2.argsort()]. Y esto también facilita la clasificación de varias listas por una columna particular de una matriz 2D: por ejemplo, sortedArray1= array1[array2[:,2].argsort()]ordenar la matriz1 (que puede tener varias columnas) por los valores en la tercera columna de la matriz2.
Aaron Bramson
40

La solución más obvia para mí es usar la keypalabra clave 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']

Tenga en cuenta que puede acortar esto a una frase si le interesa:

>>> X.sort(key=dict(zip(X, Y)).get)
senderle
fuente
2
¿Requiere esto que los valores en X sean unqiue?
Jack Peng
15

De hecho, vine aquí para ordenar una lista por una lista donde los valores coincidían.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']
nackjicholson
fuente
1
¿Es esto performant?
AFP_555
Ninguna pista. Informe lo que encuentre.
Nackjicholson
1
Esta es una mala idea. indexrealizará una búsqueda de O (N) quelist_a resulte en una O(N² log N)ordenación.
Richard
¡Gracias, no hagas esto cuando el rendimiento importa!
Nackjicholson
15

more_itertools tiene una herramienta para ordenar iterables en paralelo:

Dado

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]

Manifestación

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

Me gusta tener una lista de índices ordenados. De esa manera, puedo ordenar cualquier lista en el mismo orden que la lista de origen. Una vez que tenga una lista de índices ordenados, una simple comprensión de la lista hará el truco:

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

Tenga en cuenta que la lista de índice ordenada también se puede obtener usando numpy.argsort().

1-ijk
fuente
12

Otra alternativa, combinando varias de las respuestas.

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

Para trabajar con python3:

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

zip, ordenar por la segunda columna, devolver la primera columna.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]
riza
fuente
Nota: la clave = operator.itemgetter (1) resuelve el problema duplicado
Keith
zip no es subscriptable ... realmente debes usarlist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
raphael
@Keith qué problema duplicado?
Josh
Si hay más de una coincidencia, se obtiene la primera
Keith
3

Una línea rápida.

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

Digamos que desea que la lista a coincida con la lista b.

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

Esto es útil cuando necesita ordenar una lista más pequeña a valores más grandes. Suponiendo que la lista más grande contiene todos los valores en la lista más pequeña, se puede hacer.

Evan Lalo
fuente
Esto no resuelve la pregunta del OP. ¿Lo probaste con las listas de muestra Xy Y?
Aryeh Leib Taurog
Esta es una mala idea. indexrealizará una búsqueda de O (N) quelist_b resulte en una O(N² log N)ordenación.
Richard
1

Puede crear un pandas Series, utilizando la lista primaria como datay la otra lista como index, y luego ordenar por el índice:

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

salida:

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

Aquí está la respuesta de Whatangs si desea obtener ambas listas ordenadas (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']

Solo recuerda que Zx y Zy son tuplas. También estoy deambulando si hay una mejor manera de hacerlo.

Advertencia: si lo ejecuta con listas vacías, se bloquea.

Iraklis Moutidis
fuente
1

He creado una función más general, que clasifica más de dos listas basadas en otra, inspirada en la respuesta de @ 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
pgmank
fuente
0
list1 = ['a','b','c','d','e','f','g','h','i']
list2 = [0,1,1,0,1,2,2,0,1]

output=[]
cur_loclist = []

Para obtener valores únicos presentes en list2

list_set = set(list2)

Para encontrar la ubicación del índice en list2

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

La ubicación del índice en list2se rastrea usandocur_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
fuente
0

Esta es una vieja pregunta, pero algunas de las respuestas que veo publicadas en realidad no funcionan porque zipno son programables. Otras respuestas no molestaron aimport operator y proporcionaron más información sobre este módulo y sus beneficios aquí.

Hay al menos dos buenas expresiones idiomáticas para este problema. Comenzando con el ejemplo de entrada que proporcionó:

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

Usando el " Decorar-Ordenar-Decorar modismo "

Esto también se conoce como la transformación Schwartzian después de R. Schwartz, que popularizó este patrón en Perl en los años 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')

Tenga en cuenta que en este caso Yy Xse ordenan y comparan lexicográficamente. Es decir, Yse comparan los primeros elementos (de ); y si son iguales, Xse comparan los segundos elementos (de ), y así sucesivamente. Esto puede crear inestabilidad salidas menos que incluya los índices de la lista original para el orden lexicográfico para mantener los duplicados en su orden original.

Usando el operatormódulo

Esto le brinda un control más directo sobre cómo ordenar la entrada, para que pueda obtener estabilidad de la clasificación simplemente indicando la clave específica para clasificar. Ver más ejemplos aquí .

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
fuente