Jak posortować listę słowników według wartości słownika?

1893

Mam listę słowników i chcę, aby każdy element był sortowany według określonych wartości właściwości.

Weź pod uwagę tablicę poniżej,

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

Po posortowaniu name, powinien zostać

[{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
masi
źródło
Czytanie odpowiedzi i patrzenie na operator.itemgetter . Czy mogę sortować według wielu wartości w tym samym procesie (na przykład mamy [{'name':'Bart', 'age':10, 'note':3},{'name':'Homer','age':10,'note':2},{'name':'Vasile','age':20,'note':3}] I do użycia: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDYCJA: Przetestowano i działa, ale nie wiem, jak zrobić notatkę DESC i nazwać ASC.
Claudiu

Odpowiedzi:

2463

Może wyglądać na czystsze za pomocą klucza zamiast cmp:

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

lub jak sugerowali JFSebastian i inni,

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

Dla kompletności (jak wskazano w komentarzach fitzgeraldsteele), dodaj reverse=Truedo sortowania malejąco

newlist = sorted(l, key=itemgetter('name'), reverse=True)
Mario F.
źródło
34
Używanie klucza jest nie tylko czystsze, ale także bardziej wydajne.
jfs
5
Najszybszym sposobem byłoby dodanie instrukcji newlist.reverse (). W przeciwnym razie możesz zdefiniować porównanie, takie jak cmp = lambda x, y: - cmp (x ['name'], y ['name']).
Mario F
3
jeśli wartością sortowania jest liczba, można powiedzieć: lambda k: (k ['wiek'] * -1), aby uzyskać sortowanie odwrotne
Philluminati,
2
Dotyczy to również listy krotek, jeśli użyjesz itemgetter(i)gdzie ijest indeks elementu krotki do sortowania.
radicand
42
itemgetterakceptuje więcej niż jeden argument: itemgetter(1,2,3)jest funkcją, która zwraca krotkę obj[1], obj[2], obj[3], dzięki czemu można jej używać do wykonywania złożonych sortowań.
Bakuriu,
166
import operator

Aby posortować listę słowników według klucza = „nazwa”:

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

Aby posortować listę słowników według klucza = „wiek”:

list_of_dicts.sort(key=operator.itemgetter('age'))
Cedbeu
źródło
9
W każdym razie, aby połączyć imię i wiek? (jak w SQL ORDER BY name, age?)
monojohnny
28
@monojohnny: tak, po prostu klawisz powrotu krotki, key=lambda k: (k['name'], k['age']). (lub key=itemgetter('name', 'age')). krotki cmpporównują kolejno każdy element. to jest cholernie genialne.
Claudiu
1
W dokumentacji ( docs.python.org/2/tutorial/datastructures.html ) opcjonalny keyargument dla list.sort()nie jest opisany. Masz pomysł, gdzie to znaleźć?
TTT
2
@TTT: Zobacz dokumentację biblioteki dla listznajomych i przyjaciół.
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 będzie teraz tym, czego chcesz.

(3 lata później) Edytowano, aby dodać:

Nowy keyargument jest bardziej wydajny i bardziej przejrzysty. Lepsza odpowiedź wygląda teraz:

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

... lambda jest, IMO, łatwiejsze do zrozumienia niż operator.itemgetter, ale YMMV.

pjz
źródło
51

Jeśli chcesz posortować listę według wielu kluczy, możesz wykonać następujące czynności:

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

Jest to dość hackerskie, ponieważ polega na przekształceniu wartości w reprezentację pojedynczego ciągu w celu porównania, ale działa zgodnie z oczekiwaniami w przypadku liczb, w tym ujemnych (chociaż będziesz musiał odpowiednio sformatować swój ciąg zerami, jeśli używasz liczb)

Dologan
źródło
2
posortowane przy użyciu stabilnego timsorta, możesz wywołać sortowane kilka razy, aby posortować według kilku kryteriów
njzk2
Komentarz njzk2 nie był dla mnie od razu jasny, więc znalazłem następujące. Możesz po prostu posortować dwa razy, jak sugeruje njzk2, lub przekazać wiele argumentów do operatora.itemgetter w górnej odpowiedzi. Link: stackoverflow.com/questions/5212870/…
Permafacture
15
Nie trzeba konwertować na ciąg. Po prostu zwróć krotkę jako klucz.
Winston Ewert,
Sortowanie wiele razy jest najprostszym ogólnym rozwiązaniem bez włamań: stackoverflow.com/a/29849371/1805397
wouter bolsterlee
30
import operator
a_list_of_dicts.sort(key=operator.itemgetter('name'))

„klucz” służy do sortowania według dowolnej wartości, a „itemgetter” ustawia tę wartość do atrybutu „name” każdego elementu.

efotinis
źródło
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
źródło
21

Chyba miałeś na myśli:

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

To byłoby posortowane w następujący sposób:

sorted(l,cmp=lambda x,y: cmp(x['name'],y['name']))
Bartosz Radaczyński
źródło
19

Możesz użyć niestandardowej funkcji porównania lub przekazać funkcję, która oblicza niestandardowy klucz sortowania. Zwykle jest to bardziej wydajne, ponieważ klucz jest obliczany tylko raz na pozycję, podczas gdy funkcja porównania byłaby wywoływana wiele razy.

Możesz to zrobić w ten sposób:

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

Ale średnia biblioteka zawiera ogólną procedurę dla coraz elementy dowolnych obiektów: itemgetter. Spróbuj więc zamiast tego:

from operator import itemgetter
x = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age':10}]
sorted(x, key=itemgetter('name'))
piekarnik
źródło
19

Używając transformacji Schwartzian z Perla,

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

zrobić

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

daje

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

Więcej na temat transformacji Perla Schwartziana

W informatyce transformacja Schwartziana jest idiomem programistycznym Perla, używanym do poprawy wydajności sortowania listy pozycji. Ten idiom jest odpowiedni do sortowania opartego na porównaniu, gdy porządkowanie jest faktycznie oparte na uporządkowaniu określonej właściwości (klucza) elementów, przy czym obliczenie tej właściwości jest intensywną operacją, którą należy wykonać minimalną liczbę razy. Transformacja Schwartziana wyróżnia się tym, że nie używa nazwanych tablic tymczasowych.

Kiriloff
źródło
9
Python wspierała key=na .sortod 2,4, to jest rok 2004, to robi Transformacja Schwartza w kodzie sortowania w C; dlatego ta metoda jest przydatna tylko w Pythons 2.0-2.3. z których wszystkie mają więcej niż 12 lat.
Antti Haapala,
18

Musisz wdrożyć własną funkcję porównywania, która będzie porównywać słowniki według wartości kluczy nazw. Zobacz temat Sortowanie mini-JAK Z PythonInfo Wiki

Matej
źródło
12

czasami musimy lower()na przykład użyć

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
źródło
11

Oto alternatywne rozwiązanie ogólne - sortuje elementy dict według kluczy i wartości. Zaletą tego jest to, że nie trzeba określać kluczy, a nadal działałoby, gdyby niektórych kluczy brakowało w niektórych słownikach.

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)
Władywyrow
źródło
10

Korzystanie z pakietu pand to kolejna metoda, choć jego środowisko uruchomieniowe na dużą skalę jest znacznie wolniejsze niż bardziej tradycyjne metody proponowane przez innych:

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()

Oto kilka wartości porównawczych dla małej listy i dużej (100 tys.) Listy nagrań:

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 szloch
źródło
3
Uruchomiłem twój kod i znalazłem błąd w timeit. Timer argumentuje za Dużymi Pandami Metod: określasz „setup_small”, gdzie powinno być „setup_large”. Zmiana tego argumentu spowodowała, że ​​program działał bez ukończenia i zatrzymałem go po ponad 5 minutach. Kiedy uruchomiłem go za pomocą „timeit (1)”, Duże Pandy Metodowe zakończyły się w 7,3 sekundy, znacznie gorzej niż LC lub LC2.
clp2,
Masz całkowitą rację, to był mój niedopatrzenie. Nie polecam go już w dużych skrzyniach! Zredagowałem odpowiedź, aby po prostu pozwolić na to, ponieważ przypadek użycia jest nadal przedmiotem dyskusji.
abby sobh
6

Jeśli nie trzeba oryginał listz dictionaries, można zmodyfikować go w miejscu z sort()metodą z użyciem funkcji klawisza zwyczaj.

Klawisz funkcyjny:

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

    return d["name"]

Do listposortowania:

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

Sortowanie w miejscu:

data_one.sort(key=get_name)

Jeśli potrzebujesz oryginału list, wywołaj sorted()funkcję przekazującą go listi funkcję klucza, a następnie przypisz zwrócony sort listdo nowej zmiennej:

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

Drukowanie data_onei 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
źródło
6

Powiedzmy, że mam słownik Dz elementami poniżej. Aby posortować, użyj posortowanego argumentu w celu przekazania funkcji niestandardowej, jak poniżej:

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

Sprawdź to .

Shank_Transformer
źródło
3

Byłem wielkim fanem filtra w / lambda, jednak nie jest to najlepsza opcja, jeśli wziąć pod uwagę złożoność czasu

Pierwsza opcja

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

Druga opcja

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

Szybkie porównanie czasów wykonania

# 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 pętli, najlepiej 3: 0,736 usec na pętlę

# 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 pętli, najlepiej 3: 0,438 usec na pętlę

Bejür
źródło
2

Jeśli problemem jest wydajność, użyłbym operator.itemgetterzamiast tego, lambdaponieważ wbudowane funkcje działają szybciej niż funkcje ręcznie wykonane. itemgetterFunkcja wydaje się wykonać około 20% szybciej niż lambdaw oparciu o moje badania.

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

Podobnie, wbudowane funkcje działają szybciej niż ręcznie tworzone odpowiedniki. Na przykład mapa (operator.add, v1, v2) jest szybsza niż mapa (lambda x, y: x + y, v1, v2).

Oto porównanie prędkości sortowania używając 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

Obie techniki sortują listę w tej samej kolejności (weryfikowana przez wykonanie instrukcji końcowej w bloku kodu), ale jedna jest nieco szybsza.

swac
źródło
-1

Możesz użyć następującego kodu

sorted_dct = sorted(dct_name.items(), key = lambda x : x[1])
Loochie
źródło