Як сортувати список словників за значенням словника?


1893

У мене є список словників і хочу, щоб кожен елемент був відсортований за певними значеннями властивостей.

Враховуйте масив нижче,

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

Коли відсортовано name, має стати

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

Читання відповіді та перегляд на operator.itemgetter . Чи можу я сортувати за кількома значеннями в одному і тому ж процесі (наприклад, у нас є [{'name':'Bart', 'age':10, 'note':3},{'name':'Homer','age':10,'note':2},{'name':'Vasile','age':20,'note':3}] І використовувати: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDIT: Тестовано, і він працює, але я не знаю, як зробити зауваження DESC та назву ASC.
Клавдіу,

Відповіді:


2463

Це може виглядати чистіше, використовуючи ключ замість cmp:

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

або як запропонували JFSebastian та інші,

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

Для повноти (як зазначено у коментарях fitzgeraldsteele) додайте reverse=Trueдо сортування за спаданням

newlist = sorted(l, key=itemgetter('name'), reverse=True)

34
Використання ключа є не тільки чистішим, але й більш ефективним.
jfs

5
Найшвидшим способом було б додати нове твердження.reverse (). Інакше ви можете визначити порівняння, як cmp = lambda x, y: - cmp (x ['name'], y ['name']).
Маріо Ф

3
якщо значення сортування - це число, ви можете сказати: lambda k: (k ['age'] * -1), щоб отримати зворотне сортування
Philluminati

2
Це також стосується списку кортежів, якщо ви використовуєте, itemgetter(i)де iіндекс елемента кортежа для сортування.
radicand

42
itemgetterприймає більше ніж один аргумент: itemgetter(1,2,3)це функція, яка повертає кортеж на зразок obj[1], obj[2], obj[3], тож ви можете використовувати його для виконання складних сортів.
Бакуріу

166
import operator

Сортувати список словників за ключем = 'ім'я':

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

Сортувати список словників за ключем = 'age':

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

9
Все-таки поєднувати ім’я та вік? (як у SQL ORDER BY ім'я, вік?)
monojohnny

28
@monojohnny: так, просто ключ повернути кортеж, key=lambda k: (k['name'], k['age']). (або key=itemgetter('name', 'age')). кортежі cmpпорівнюватимуть кожен елемент по черзі. це криваво блискуче.
Клавдіу

1
У документації ( docs.python.org/2/tutorial/datastructures.html ) необов'язковий keyаргумент для list.sort()не описаний. Будь-яка ідея, де це знайти?
TTT

2
@TTT: Перегляньте документацію бібліотеки для listдрузів.
Кевін

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

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

my_list тепер буде тим, що ви хочете.

(3 роки потому) Відредаговано, щоб додати:

Новий keyаргумент є більш ефективним та охайним. Краща відповідь зараз виглядає так:

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

... лямбда, IMO, легше зрозуміти operator.itemgetter, але YMMV.


51

Якщо ви хочете сортувати список за кількома клавішами, ви можете зробити наступне:

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

Це досить хакітсько, оскільки він покладається на перетворення значень в єдине представлення рядків для порівняння, але працює так, як очікувалося, для чисел, включаючи негативні (хоча вам потрібно буде правильно відформатувати рядок з нульовими прокладками, якщо ви використовуєте числа)


2
відсортовано за допомогою стабільного темсорту, ви можете зателефонувати відсортованому кілька разів, щоб мати сортування за кількома критеріями
njzk2

Коментар njzk2 не був мені відразу зрозумілим, тому я знайшов наступне. Ви можете просто сортувати двічі, як пропонує njzk2, або передати декілька аргументів operator.itemgetter у верхній відповіді. Посилання: stackoverflow.com/questions/5212870 / ...
Permafacture

15
Не потрібно перетворювати на рядок. Просто поверніть кортеж як ключ.
Вінстон Еверт

Сортування кілька разів є найпростішим рішенням загальних без зламів: stackoverflow.com/a/29849371/1805397
Wouter bolsterlee

30
import operator
a_list_of_dicts.sort(key=operator.itemgetter('name'))

'ключ' використовується для сортування за довільним значенням, а "itemgetter" встановлює це значення для атрибута "name" кожного елемента.


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


19

Ви можете використовувати спеціальну функцію порівняння або передати функцію, яка обчислює спеціальний ключ сортування. Зазвичай це більш ефективно, оскільки ключ обчислюється лише один раз на предмет, тоді як функція порівняння буде називатися набагато більше разів.

Ви можете це зробити так:

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

Але стандартна бібліотека містить загальну процедуру для отримання елементів довільних об'єктів: itemgetter. Тому спробуйте це замість цього:

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

19

Використовуючи трансформацію Шварца з Perl,

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

робити

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

Детальніше про перетворення Перла Шварцана

В інформатиці трансформація Шварца - це ідіома програмування Perl, яка використовується для підвищення ефективності сортування списку елементів. Ця ідіома підходить для сортування на основі порівняння, коли впорядкування фактично засноване на впорядкуванні певного властивості (ключа) елементів, де обчислення цього властивості - це інтенсивна операція, яку слід виконувати мінімально кілька разів. Трансформація Шварца відрізняється тим, що вона не використовує названі тимчасові масиви.


9
Python підтримує функцію key=for .sort2.4 з 2004 року, тобто 2004 рік, він робить трансформацію Шварца в межах сортувального коду в C; таким чином, цей метод корисний лише на Pythons 2.0-2.3. всім яким більше 12 років.
Антті Хаапала


12

колись нам потрібно скористатися, lower()наприклад

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

11

Ось альтернативне загальне рішення - воно сортує елементи дикту за ключами та значеннями. Перевага його - не потрібно вказувати ключі, і все одно він буде працювати, якщо в деяких словниках відсутні ключі.

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)

10

Використання пакету pandas - це ще один метод, хоча час його виконання у великих масштабах набагато повільніше, ніж традиційні методи, запропоновані іншими:

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

Ось деякі базові значення для крихітного списку та великого (100k +) списку диктів:

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

3
Я запустив ваш код і виявив помилку в timeit.Timer аргументує для великих методів Pandas: ви вказуєте "setup_small" там, де має бути "setup_large". Зміна цього аргументу призвела до запуску програми без закінчення, і я зупинив її через більше 5 хвилин. Коли я запустив його з "timeit (1)", Панда з великим методом закінчилася за 7,3 сек, набагато гірше, ніж LC або LC2.
clp2

Ви абсолютно праві, це був досить недогляд з мого боку. Я більше не рекомендую його для великих випадків! Я відредагував відповідь, щоб просто дозволити це як можливість, випадок використання все ще підходить для обговорення.
abby sobh

6

Якщо вам не потрібен оригінал listз dictionaries, ви можете змінити його в місці з sort()методом , використовуючи призначену для користувача функцію ключа.

Основна функція:

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

    return d["name"]

listДля сортування:

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

Сортування його на місці:

data_one.sort(key=get_name)

Якщо вам потрібен оригінал list, зателефонуйте sorted()функції, що передає його, listі клавіші, а потім призначте повернене відсортоване listновій змінній:

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

Друк data_oneта new_data.

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

6

Скажімо, у мене Dвнизу є словник з елементами. Для сортування просто використовуйте ключовий аргумент у сортованому для передачі спеціальної функції, як показано нижче:

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

Перевірте це .


3

Я був великим шанувальником фільтру з лямбда, однак це не найкращий варіант, якщо враховувати складність часу

Перший варіант

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

Другий варіант

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

Швидке порівняння exec часів

# 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 петель, найкраще 3: 0,736 використання в циклі

# 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 петель, найкраще 3: 0,438 використання в циклі


2

Якщо продуктивність викликає занепокоєння, я б використовував operator.itemgetterзамість того, lambdaяк вбудовані функції виконують швидше, ніж ручні функції. itemgetterЗдається, ця функція виконує приблизно на 20% швидше, ніж lambdaна основі мого тестування.

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

Аналогічно, вбудовані функції працюють швидше, ніж вбудовані вручну еквіваленти. Наприклад, map (operator.add, v1, v2) швидше, ніж map (лямбда x, y: x + y, v1, v2).

Ось порівняння сортування швидкості , використовуючи lambdaпроти 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

Обидві методи сортують список в одному порядку (перевіряється виконанням остаточного висловлювання в кодовому блоці), але одна трохи швидша.


-1

Ви можете використовувати наступний код

sorted_dct = sorted(dct_name.items(), key = lambda x : x[1])
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.