Como ordeno uma lista de dicionários por um valor do dicionário?

1898

Eu tenho uma lista de dicionários e quero que cada item seja classificado por valores de propriedade específicos.

Leve em consideração a matriz abaixo,

[{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

Quando ordenado por name, deve tornar-se

[{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
masi
fonte
Lendo a resposta e procurando em operator.itemgetter . Posso classificar em vários valores no mesmo processo (por exemplo, temos [{'name':'Bart', 'age':10, 'note':3},{'name':'Homer','age':10,'note':2},{'name':'Vasile','age':20,'note':3}] E para usar: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDIT: Testado e está funcionando, mas não sei como anotar DESC e nomear ASC.
Claudiu

Respostas:

2470

Pode parecer mais limpo usando uma chave em vez de um cmp:

newlist = sorted(list_to_be_sorted, key=lambda k: k['name']) 

ou como JFSebastian e outros sugeriram,

from operator import itemgetter
newlist = sorted(list_to_be_sorted, key=itemgetter('name')) 

Para completar (como indicado nos comentários de fitzgeraldsteele), adicione reverse=Trueà classificação decrescente

newlist = sorted(l, key=itemgetter('name'), reverse=True)
Mario F
fonte
34
O uso da chave não é apenas mais limpo, mas também mais eficiente.
jfs
5
A maneira mais rápida seria adicionar uma instrução newlist.reverse (). Caso contrário, você pode definir uma comparação como cmp = lambda x, y: - cmp (x ['name'], y ['name']).
Mario F
3
se o valor tipo é um número que você poderia dizer: lambda k: (k [ 'idade'] * -1) para obter uma espécie inversa
Philluminati
2
Isso também se aplica a uma lista de tuplas, se você usar itemgetter(i)onde iestá o índice do elemento de tupla para classificar.
Radicand
42
itemgetteraceita mais de um argumento: itemgetter(1,2,3)é uma função que retorna uma tupla como obj[1], obj[2], obj[3], para que você possa usá-la para fazer classificações complexas.
Bakuriu
166
import operator

Para classificar a lista de dicionários por key = 'name':

list_of_dicts.sort(key=operator.itemgetter('name'))

Para classificar a lista de dicionários por key = 'age':

list_of_dicts.sort(key=operator.itemgetter('age'))
cedbeu
fonte
9
Enfim, para combinar nome e idade? (como em ORDEM SQL por nome, idade?) #
7990 monojohnny
28
@monojohnny: sim, basta ter a chave retornar uma tupla key=lambda k: (k['name'], k['age']),. (ou key=itemgetter('name', 'age')) tuplas cmpcomparará cada elemento por vez. é brilhante demais.
Claudiu 4/13
1
Na documentação ( docs.python.org/2/tutorial/datastructures.html ) o keyargumento opcional para list.sort()não é descrito. Alguma idéia de onde encontrar isso?
TTT 21/02
2
@TTT: Veja a documentação da biblioteca para liste amigos.
19715 Kevin
64
my_list = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

my_list.sort(lambda x,y : cmp(x['name'], y['name']))

my_list agora será o que você quer.

(3 anos depois) Editado para adicionar:

O novo keyargumento é mais eficiente e mais organizado. Uma resposta melhor agora se parece com:

my_list = sorted(my_list, key=lambda k: k['name'])

... o lambda é, na IMO, mais fácil de entender do que operator.itemgetter, mas YMMV.

pjz
fonte
51

Se você deseja classificar a lista por várias chaves, faça o seguinte:

my_list = [{'name':'Homer', 'age':39}, {'name':'Milhouse', 'age':10}, {'name':'Bart', 'age':10} ]
sortedlist = sorted(my_list , key=lambda elem: "%02d %s" % (elem['age'], elem['name']))

É um tanto tolo, pois depende da conversão dos valores em uma única representação de string para comparação, mas funciona como esperado para números incluindo negativos (embora você precise formatar sua string adequadamente com zero, se estiver usando números)

Dologan
fonte
2
classificados usando timsort que é estável, você pode chamar classificadas várias vezes para ter uma espécie em vários critérios
njzk2
O comentário de njzk2 não foi imediatamente claro para mim, então eu achei o seguinte. Você pode classificar apenas o dobro do sugerido pelo njzk2 ou passar vários argumentos para operator.itemgetter na resposta superior. Link: stackoverflow.com/questions/5212870/…
Permafacture
15
Não há necessidade de converter para string. Basta retornar uma tupla como chave.
Winston Ewert
Classificando várias vezes é a solução genérica mais fácil, sem hacks: stackoverflow.com/a/29849371/1805397
wouter bolsterlee
30
import operator
a_list_of_dicts.sort(key=operator.itemgetter('name'))

'key' é usado para classificar por um valor arbitrário e 'itemgetter' define esse valor para o atributo 'name' de cada item.

efotinis
fonte
27
a = [{'name':'Homer', 'age':39}, ...]

# This changes the list a
a.sort(key=lambda k : k['name'])

# This returns a new list (a is not modified)
sorted(a, key=lambda k : k['name']) 
forzagreen
fonte
21

Eu acho que você quis dizer:

[{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

Isso seria classificado assim:

sorted(l,cmp=lambda x,y: cmp(x['name'],y['name']))
Bartosz Radaczyński
fonte
19

Você pode usar uma função de comparação personalizada ou pode transmitir uma função que calcula uma chave de classificação personalizada. Isso geralmente é mais eficiente, pois a chave é calculada apenas uma vez por item, enquanto a função de comparação seria chamada muitas mais vezes.

Você poderia fazer assim:

def mykey(adict): return adict['name']
x = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age':10}]
sorted(x, key=mykey)

Mas a biblioteca padrão contém uma rotina genérica para obter itens de objetos arbitrários: itemgetter. Então tente isso:

from operator import itemgetter
x = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age':10}]
sorted(x, key=itemgetter('name'))
Owen
fonte
19

Usando a transformação schwartziana do Perl,

py = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

Faz

sort_on = "name"
decorated = [(dict_[sort_on], dict_) for dict_ in py]
decorated.sort()
result = [dict_ for (key, dict_) in decorated]

>>> result
[{'age': 10, 'name': 'Bart'}, {'age': 39, 'name': 'Homer'}]

Mais sobre a transformação de Perl Schwartzian

Na ciência da computação, a transformação Schwartziana é um idioma de programação Perl usado para melhorar a eficiência da classificação de uma lista de itens. Esse idioma é apropriado para a classificação baseada em comparação quando a ordem é realmente baseada na ordem de uma determinada propriedade (a chave) dos elementos, onde o cálculo dessa propriedade é uma operação intensiva que deve ser executada um número mínimo de vezes. A Transformação Schwartziana é notável por não usar matrizes temporárias nomeadas.

Kiriloff
fonte
9
Python tem apoiado o key=para .sortdesde 2,4, que é ano de 2004, ele faz o Schwartziana transformar dentro do código de classificação, no C; portanto, esse método é útil apenas no Pythons 2.0-2.3. todos com mais de 12 anos.
Antti Haapala
12

em algum momento precisamos usar, lower()por exemplo

lists = [{'name':'Homer', 'age':39},
  {'name':'Bart', 'age':10},
  {'name':'abby', 'age':9}]

lists = sorted(lists, key=lambda k: k['name'])
print(lists)
# [{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}, {'name':'abby', 'age':9}]

lists = sorted(lists, key=lambda k: k['name'].lower())
print(lists)
# [ {'name':'abby', 'age':9}, {'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
uingtea
fonte
11

Aqui está a solução geral alternativa - ela classifica os elementos do ditado por chaves e valores. A vantagem disso - não é necessário especificar chaves, e ainda funcionaria se algumas chaves estiverem ausentes em alguns dicionários.

def sort_key_func(item):
    """ helper function used to sort list of dicts

    :param item: dict
    :return: sorted list of tuples (k, v)
    """
    pairs = []
    for k, v in item.items():
        pairs.append((k, v))
    return sorted(pairs)
sorted(A, key=sort_key_func)
vvladymyrov
fonte
10

O uso do pacote pandas é outro método, embora o tempo de execução em larga escala seja muito mais lento que os métodos mais tradicionais propostos por outros:

import pandas as pd

listOfDicts = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]
df = pd.DataFrame(listOfDicts)
df = df.sort_values('name')
sorted_listOfDicts = df.T.to_dict().values()

Aqui estão alguns valores de referência para uma lista minúscula e uma lista grande (100k +) de dictos:

setup_large = "listOfDicts = [];\
[listOfDicts.extend(({'name':'Homer', 'age':39}, {'name':'Bart', 'age':10})) for _ in range(50000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(listOfDicts);"

setup_small = "listOfDicts = [];\
listOfDicts.extend(({'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(listOfDicts);"

method1 = "newlist = sorted(listOfDicts, key=lambda k: k['name'])"
method2 = "newlist = sorted(listOfDicts, key=itemgetter('name')) "
method3 = "df = df.sort_values('name');\
sorted_listOfDicts = df.T.to_dict().values()"

import timeit
t = timeit.Timer(method1, setup_small)
print('Small Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_small)
print('Small Method LC2: ' + str(t.timeit(100)))
t = timeit.Timer(method3, setup_small)
print('Small Method Pandas: ' + str(t.timeit(100)))

t = timeit.Timer(method1, setup_large)
print('Large Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_large)
print('Large Method LC2: ' + str(t.timeit(100)))
t = timeit.Timer(method3, setup_large)
print('Large Method Pandas: ' + str(t.timeit(1)))

#Small Method LC: 0.000163078308105
#Small Method LC2: 0.000134944915771
#Small Method Pandas: 0.0712950229645
#Large Method LC: 0.0321750640869
#Large Method LC2: 0.0206089019775
#Large Method Pandas: 5.81405615807
abby sobh
fonte
3
Eu executei seu código e encontrei um erro nos argumentos timeit.Timer para Large Method Pandas: você especifica "setup_small" onde deve ser "setup_large". Alterar esse argumento fez com que o programa fosse executado sem concluir e eu o parei após mais de 5 minutos. Quando eu o executei com "timeit (1)", o Large Method Pandas terminou em 7,3 segundos, muito pior que o LC ou o LC2.
Clp2 7/11
Você está certo, isso foi um descuido da minha parte. Já não o recomendo para casos grandes! Eu editei a resposta para simplesmente permitir isso como uma possibilidade, o caso de uso ainda está em debate.
Abby sobh
6

Se você não precisa do original listde dictionaries, você pode modificá-lo no local com sort()método utilizando uma função chave personalizado.

Função chave:

def get_name(d):
    """ Return the value of a key in a dictionary. """

    return d["name"]

A listser classificado:

data_one = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age': 10}]

Classificando-o no local:

data_one.sort(key=get_name)

Se você precisar do original list, chame a sorted()função que passa por ele liste a função da tecla e atribua o retornado classificado lista uma nova variável:

data_two = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age': 10}]
new_data = sorted(data_two, key=get_name)

Impressão data_onee new_data.

>>> print(data_one)
[{'name': 'Bart', 'age': 10}, {'name': 'Homer', 'age': 39}]
>>> print(new_data)
[{'name': 'Bart', 'age': 10}, {'name': 'Homer', 'age': 39}]
Srisaila
fonte
6

Digamos que eu tenho um dicionário Dcom elementos abaixo. Para classificar, use o argumento-chave em ordenado para passar a função personalizada como abaixo:

D = {'eggs': 3, 'ham': 1, 'spam': 2}
def get_count(tuple):
    return tuple[1]

sorted(D.items(), key = get_count, reverse=True)
# or
sorted(D.items(), key = lambda x: x[1], reverse=True)  # avoiding get_count function call

Veja isso .

Shank_Transformer
fonte
3

Eu tenho sido um grande fã de filtro w / lambda no entanto, não é a melhor opção se você considerar a complexidade do tempo

Primeira opção

sorted_list = sorted(list_to_sort, key= lambda x: x['name'])
# returns list of values

Segunda opçao

list_to_sort.sort(key=operator.itemgetter('name'))
#edits the list, does not return a new list

Comparação rápida de tempos de execução

# First option
python3.6 -m timeit -s "list_to_sort = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}, {'name':'Faaa', 'age':57}, {'name':'Errr', 'age':20}]" -s "sorted_l=[]" "sorted_l = sorted(list_to_sort, key=lambda e: e['name'])"

1000000 loops, o melhor de 3: 0,736 usec por loop

# Second option 
python3.6 -m timeit -s "list_to_sort = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}, {'name':'Faaa', 'age':57}, {'name':'Errr', 'age':20}]" -s "sorted_l=[]" -s "import operator" "list_to_sort.sort(key=operator.itemgetter('name'))"

1000000 loops, o melhor de 3: 0,438 usec por loop

Bejür
fonte
2

Se o desempenho é uma preocupação, eu usaria, em operator.itemgettervez de lambdaas funções internas, executarem mais rapidamente que as funções artesanais. A itemgetterfunção parece executar aproximadamente 20% mais rápido do que com lambdabase nos meus testes.

Em https://wiki.python.org/moin/PythonSpeed :

Da mesma forma, as funções internas são executadas mais rapidamente que os equivalentes criados à mão. Por exemplo, o mapa (operator.add, v1, v2) é mais rápido que o mapa (lambda x, y: x + y, v1, v2).

Aqui está uma comparação de triagem velocidade usando lambdavs itemgetter.

import random
import operator

# create a list of 100 dicts with random 8-letter names and random ages from 0 to 100.
l = [{'name': ''.join(random.choices(string.ascii_lowercase, k=8)), 'age': random.randint(0, 100)} for i in range(100)]

# Test the performance with a lambda function sorting on name
%timeit sorted(l, key=lambda x: x['name'])
13 µs ± 388 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

# Test the performance with itemgetter sorting on name
%timeit sorted(l, key=operator.itemgetter('name'))
10.7 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

# Check that each technique produces same sort order
sorted(l, key=lambda x: x['name']) == sorted(l, key=operator.itemgetter('name'))
True

Ambas as técnicas classificam a lista na mesma ordem (verificada pela execução da instrução final no bloco de código), mas uma é um pouco mais rápida.

swac
fonte
-1

Você pode usar o seguinte código

sorted_dct = sorted(dct_name.items(), key = lambda x : x[1])
Loochie
fonte