Classificando a lista com base nos valores de outra lista?

370

Eu tenho uma lista de seqüências de caracteres como esta:

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

Qual é a maneira mais curta de classificar X usando valores de Y para obter a seguinte saída?

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

A ordem dos elementos com a mesma "chave" não importa. Posso recorrer ao uso de forconstruções, mas estou curioso para saber se há uma maneira mais curta. Alguma sugestão?

lenda
fonte
A resposta de riza pode ser útil ao plotar dados, pois zip (* classificado (zip (X, Y), chave = par lambda: par [0])) retorna X e Y classificados com valores de X.
jojo

Respostas:

479

Código mais curto

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

Exemplo:

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

De um modo geral

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

Explicado:

  1. zipos dois lists.
  2. crie um novo, classificado com listbase no zipuso sorted().
  3. usando uma compreensão de lista, extraia os primeiros elementos de cada par dos classificados, zipados list.

Para obter mais informações sobre como definir \ use o keyparâmetro, bem como a sortedfunção em geral, dê uma olhada nisso .


Whatang
fonte
117
Isso está correto, mas acrescentarei que, se você estiver tentando classificar várias matrizes pela mesma matriz, isso não funcionará necessariamente como esperado, pois a chave que está sendo usada para classificar é (y, x) , não apenas y. Em vez disso, deve usar [x para (y, x) em ordenadas (ZIP (Y, X), chave = lambda par: par [0])]
gms7777
11
boa solução! Mas deve ser: A lista é ordenada em relação ao primeiro elemento dos pares e a compreensão extrai o 'segundo' elemento dos pares.
MasterControlProgram
Esta solução é ruim quando se trata de armazenamento. Uma classificação no local é preferida sempre que possível.
Hatefiend 30/06/19
107

Feche as duas listas, classifique-as e pegue as partes que deseja:

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

Combine-os para obter:

[x for y, x in sorted(zip(Y, X))]
Ned Batchelder
fonte
11
Isso é bom se Xé uma lista de str, mas tenha cuidado se houver uma possibilidade que <não está definido para alguns pares de itens em X, por exemplo - se alguns deles foramNone
John La Rooy
11
Quando tentamos usar a classificação sobre um objeto zip, AttributeError: 'zip' object has no attribute 'sort'é o que estou obtendo a partir de agora.
Ash Upadhyay
2
Você está usando o Python 3. No Python 2, o zip produziu uma lista. Agora, ele produz um objeto iterável. sorted(zip(...))ainda deve funcionar, ou: them = list(zip(...)); them.sort()
Ned Batchelder
77

Além disso, se você não se importa em usar matrizes numpy (ou na verdade já está lidando com matrizes numpy ...), aqui está outra solução interessante:

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]

Encontrei-o aqui: http://scienceoss.com/sort-one-list-by-another-list/

Tom
fonte
11
Para matrizes / vetores maiores, esta solução com numpy é benéfica!
MasterControlProgram
11
Se eles já são matrizes numpy, é simplesmente sortedArray1= array1[array2.argsort()]. E isso também facilita a classificação de várias listas por uma coluna específica de uma matriz 2D: por exemplo, sortedArray1= array1[array2[:,2].argsort()]a classificação da matriz1 (que pode ter várias colunas) pelos valores da terceira coluna da matriz2.
Aaron Bramson
40

A solução mais óbvia para mim é usar a keypalavra - chave 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']

Observe que você pode reduzi-lo para uma linha, se desejar:

>>> X.sort(key=dict(zip(X, Y)).get)
remetente
fonte
2
Isso requer que os valores em X sejam uniqiue?
Jack Peng
15

Na verdade, eu vim aqui procurando classificar uma lista por uma lista em que os valores correspondiam.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']
nackjicholson
fonte
11
Este é um desempenho?
AFP_555
Nenhuma pista. Relate o que encontrou.
nackjicholson
11
Esta é uma má ideia. indexexecutará um (N) O pesquisa no list_aresultando em um O(N² log N)tipo.
Richard
Obrigado, não faça isso quando o desempenho for importante!
nackjicholson
15

more_itertools possui uma ferramenta para classificar iterables em 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]

Demo

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

Eu gosto de ter uma lista de índices classificados. Dessa forma, eu posso classificar qualquer lista na mesma ordem que a lista de fontes. Depois de ter uma lista de índices classificados, uma simples compreensão da lista fará o truque:

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

Observe que a lista de índice classificada também pode ser obtida usando numpy.argsort().

1-ijk
fonte
12

Outra alternativa, combinando várias das respostas.

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

Para trabalhar com python3:

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

zip, classifique pela segunda coluna, retorne a primeira coluna.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]
riza
fonte
Nota: a chave = operator.itemgetter (1) resolve o problema duplicado
Keith
zip não é subscrito ... você deve realmente usarlist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
raphael
@ Keith que questão duplicada?
Josh
Se houver mais de uma correspondência, obtém a primeira
Keith
3

Uma linha 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 você queira que a lista a corresponda à lista b.

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

Isso é útil quando é necessário solicitar uma lista menor com valores maiores. Supondo que a lista maior contenha todos os valores da lista menor, isso pode ser feito.

Evan Lalo
fonte
Isso não resolve a questão do OP. Você tentou com as listas de amostras Xe Y?
Aryeh Leib Taurog
Esta é uma má ideia. indexexecutará um (N) O pesquisa no list_bresultando em um O(N² log N)tipo.
Richard
1

Você pode criar um pandas Series, usando a lista principal como datae a outra lista como indexe, em seguida, basta classificar pelo índice:

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

resultado:

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

Aqui está a resposta do Whatangs se você deseja obter as duas listas classificadas (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']

Lembre-se de que Zx e Zy são tuplas. Também estou vagando se há uma maneira melhor de fazer isso.

Aviso: Se você executá-lo com listas vazias, ele trava.

Iraklis Moutidis
fonte
1

Criei uma função mais geral, que classifica mais de duas listas com base em outra, inspirada na resposta 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
fonte
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 obter valores exclusivos presentes em list2

list_set = set(list2)

Para encontrar o local do índice em list2

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

A localização do índice list2é rastreada 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
fonte
0

Esta é uma pergunta antiga, mas algumas das respostas que vejo postadas não funcionam de fato porque zipnão são programáveis. Outras respostas não se deram ao trabalho de import operatorfornecer mais informações sobre este módulo e seus benefícios aqui.

Existem pelo menos duas boas expressões para esse problema. Começando com a entrada de exemplo que você forneceu:

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

Usando o idioma " Decorar-Classificar-Undecorate "

Isso também é conhecido como Schwartzian_transform após R. Schwartz, que popularizou esse padrão em Perl nos anos 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')

Note-se que, neste caso, Ye Xsão ordenados e comparados lexicographically. Ou seja, os primeiros itens (de Y) são comparados; e se forem iguais, os segundos itens (de X) serão comparados e assim por diante. Isso pode criar instabilidade resultados menos que você inclua os índices da lista original da ordem lexicográfica para manter as duplicatas na ordem original.

Usando o operatormódulo

Isso fornece um controle mais direto sobre como classificar a entrada, para que você possa obter estabilidade na classificação simplesmente indicando a chave específica a ser classificada. Veja mais exemplos aqui .

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
fonte