Classificando uma lista Python por dois campos

173

Eu tenho a seguinte lista criada a partir de um CSV classificado

list1 = sorted(csv1, key=operator.itemgetter(1))

Na verdade, eu gostaria de classificar a lista por dois critérios: primeiro pelo valor no campo 1 e depois pelo valor no campo 2. Como faço isso?

meio cheio
fonte
3
Possível duplicata de Classificar uma lista por vários atributos?
Chris_Rands
Deixamos essa questão de pé e apenas restringimos seu escopo a "lista de listas de comprimento de dois tipos internos (por exemplo, string / int / float)" . Ou também permitimos "lista de objetos definidos pelo usuário" , como o título sugere, também é permitido; nesse caso, a resposta é "Definir __lt__()método em sua classe ou herdar de alguma classe que faz" ? Isso tornaria um canônico muito melhor.
SMCI

Respostas:

158

como isso:

import operator
list1 = sorted(csv1, key=operator.itemgetter(1, 2))
mouad
fonte
1
+1: Mais elegante que o meu. Esqueci que o itemgetter pode receber vários índices.
Dappawit 6/03/11
7
operatoré um módulo que precisa ser importado.
trapicki
3
como vou proceder se quiser classificar ascendente em um elemento e decrescente em outro, usando o itemgetter ??.
ashish
3
@ashish, veja minha resposta abaixo com as funções lambda, isso é claro, classifique por "-x [1]" ou até "x [0] + x [1]" se desejar
jaap
e se um critério estiver no modo reverso?
precisa saber é o seguinte
328

Não há necessidade de importar nada ao usar funções lambda.
O seguinte classifica listpelo primeiro elemento e depois pelo segundo elemento.

sorted(list, key=lambda x: (x[0], -x[1]))
jaap
fonte
12
Agradável. Como você observou no comentário à resposta principal acima, esta é a melhor (apenas?) Maneira de fazer várias classificações com diferentes ordens de classificação. Talvez destaque isso. Além disso, seu texto não indica que você classificou decrescente no segundo elemento.
PeterVermont
2
@ user1700890 Eu estava assumindo que o campo já era string. Ele deve classificar as strings em ordem alfabética por padrão. Você deve postar sua própria pergunta separadamente no SO, se não estiver especificamente relacionada à resposta aqui ou à pergunta original do OP.
Pbible
5
o que significa o -in -x[1]?
janeiro
7
@jan é tipo reverso
jaap
3
Não funcionará em um caso específico. A solução aceita também não funcionará. Por exemplo, as colunas a serem usadas como chaves são todas as strings que não podem ser convertidas em números. Em segundo lugar, deseja-se classificar em ordem crescente por uma coluna e ordem decrescente por outra coluna.
coder.in.me 25/09
20

O Python tem uma classificação estável, portanto, desde que o desempenho não seja um problema, a maneira mais simples é classificá-lo pelo campo 2 e depois classificá-lo novamente pelo campo 1.

Isso fornecerá o resultado desejado, o único problema é que, se for uma lista grande (ou você quiser classificá-la com frequência), chamar a classificação duas vezes poderá ser uma sobrecarga inaceitável.

list1 = sorted(csv1, key=operator.itemgetter(2))
list1 = sorted(list1, key=operator.itemgetter(1))

Fazer dessa maneira também facilita lidar com a situação em que você deseja que algumas das colunas sejam classificadas inversamente, basta incluir o parâmetro 'reverse = True' quando necessário.

Caso contrário, você pode passar vários parâmetros para o itemgetter ou criar manualmente uma tupla. Provavelmente será mais rápido, mas tem o problema de não generalizar bem se algumas das colunas quiserem ser classificadas inversamente (as colunas numéricas ainda podem ser revertidas negando-as, mas isso impede que a classificação seja estável).

Portanto, se você não precisar de nenhuma coluna classificada inversamente, vá para vários argumentos para itemgetter, se precisar, e as colunas não são numéricas ou você deseja manter a classificação estável por várias classificações consecutivas.

Editar: para os comentadores que têm problemas para entender como isso responde à pergunta original, aqui está um exemplo que mostra exatamente como a natureza estável da classificação garante que podemos fazer classificações separadas em cada chave e terminar com os dados classificados em vários critérios:

DATA = [
    ('Jones', 'Jane', 58),
    ('Smith', 'Anne', 30),
    ('Jones', 'Fred', 30),
    ('Smith', 'John', 60),
    ('Smith', 'Fred', 30),
    ('Jones', 'Anne', 30),
    ('Smith', 'Jane', 58),
    ('Smith', 'Twin2', 3),
    ('Jones', 'John', 60),
    ('Smith', 'Twin1', 3),
    ('Jones', 'Twin1', 3),
    ('Jones', 'Twin2', 3)
]

# Sort by Surname, Age DESCENDING, Firstname
print("Initial data in random order")
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred''')
DATA.sort(key=lambda row: row[1])

for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.''')
DATA.sort(key=lambda row: row[2], reverse=True)
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.
''')
DATA.sort(key=lambda row: row[0])
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

Este é um exemplo executável, mas para salvar as pessoas que o executam, a saída é:

Initial data in random order
Jones      Jane       58
Smith      Anne       30
Jones      Fred       30
Smith      John       60
Smith      Fred       30
Jones      Anne       30
Smith      Jane       58
Smith      Twin2      3
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Jones      Twin2      3

First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Jones      Jane       58
Smith      Jane       58
Smith      John       60
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.
Smith      John       60
Jones      John       60
Jones      Jane       58
Smith      Jane       58
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.

Jones      John       60
Jones      Jane       58
Jones      Anne       30
Jones      Fred       30
Jones      Twin1      3
Jones      Twin2      3
Smith      John       60
Smith      Jane       58
Smith      Anne       30
Smith      Fred       30
Smith      Twin1      3
Smith      Twin2      3

Observe em particular como, no segundo passo, o reverse=Trueparâmetro mantém os primeiros nomes em ordem, enquanto a simples classificação e a reversão da lista perderiam a ordem desejada para a terceira chave de classificação.

Duncan
fonte
1
A classificação estável não significa que não se esqueça da classificação anterior. Esta resposta está errada.
Mike Axiak
7
Classificação estável significa que você pode classificar pelas colunas a, b, c simplesmente classificando pela coluna c e depois b e a. A menos que você queira expandir seu comentário, acho que é você quem está enganado.
Duncan
7
Esta resposta está definitivamente correta, embora para listas maiores seja unideal: se a lista já estiver parcialmente classificada, você perderá a maior parte da otimização da classificação do Python, misturando muito mais a lista. @ Mike, você está incorreto; Sugiro realmente testar respostas antes de declara-las erradas.
perfil completo de Glenn Maynard
6
@MikeAxiak: docs.python.org/2/library/stdtypes.html#index-29 afirma no comentário 9: Iniciando com o Python 2.3, é garantido que o método sort () seja estável. Uma classificação é estável se garantir não alterar a ordem relativa dos elementos que se comparam da mesma forma - isso é útil para classificar em várias passagens (por exemplo, classificar por departamento e depois por salário).
trapicki
Isso não está correto porque isso não responde à pergunta que ele fez. ele quer uma lista classificada pelo primeiro índice e, no caso de haver laços no primeiro índice, ele deseja usar o segundo índice como critério de classificação. Uma classificação estável apenas garante que todas as coisas sejam iguais, a ordem original passada será a ordem em que os itens aparecerão.
Jon
14
list1 = sorted(csv1, key=lambda x: (x[1], x[2]) )
dappawit
fonte
4
Eu não acho que tuple()pode receber dois argumentos (ou melhor, três, se contarmos com self)
Filipe Correia
3
tuple toma só pode receber um argumento
therealprashant
1
returndeclaração deve ser return tuple((x[1], x[2]))ou simplesmente return x[1], x[2]. Consulte @jaap resposta abaixo se você está procurando para a classificação em diferentes direções
Jo Kachikaran
… Ou tuple(x[1:3]), se você quiser usar o construtor de tupla por algum motivo, em vez de apenas uma lista de exibição de tupla x[1], x[2]. Ou keyfunc = operator.itemgetter(1, 2)e nem mesmo escreva uma função.
abarnert
3
employees.sort(key = lambda x:x[1])
employees.sort(key = lambda x:x[0])

Também podemos usar .sort com lambda 2 vezes, porque a classificação python está no lugar e é estável. Isso primeiro classificará a lista de acordo com o segundo elemento, x [1]. Em seguida, ele classificará o primeiro elemento, x [0] (prioridade mais alta).

employees[0] = Employee's Name
employees[1] = Employee's Salary

Isso é equivalente ao seguinte: employee.sort (key = lambda x: (x [0], x [1]))

Deepak Yadav
fonte
1
não, essa regra de classificação precisa ter precedência e depois a segunda.
CodeFarmer 8/03/19
1

Em ordem crescente, você pode usar:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]))

ou em ordem decrescente, você pode usar:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]),reverse=True)
Majid Arasteh
fonte
0

A lista de classificação dos dictos usando abaixo classificará a lista em ordem decrescente na primeira coluna como salário e na segunda coluna como idade

d=[{'salary':123,'age':23},{'salary':123,'age':25}]
d=sorted(d, key=lambda i: (i['salary'], i['age']),reverse=True)

Resultado: [{'salário': 123, 'idade': 25}, {'salário': 123, 'idade': 23}]

Saurabh
fonte