Как отсортировать список словарей по значению словаря?

1899

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

Примите во внимание массив ниже,

[{'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}] And для использования: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDIT: протестировано, и оно работает, но я не знаю, как сделать заметку DESC и назвать ASC.
Claudiu

Ответы:

2471

Это может выглядеть чище, используя ключ вместо 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
Самый быстрый способ - добавить оператор newlist.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], поэтому вы можете использовать его для выполнения сложных сортировок.
Бакуриу
167
import operator

Чтобы отсортировать список словарей по ключу = 'name':

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

Чтобы отсортировать список словарей по ключу = 'возраст':

list_of_dicts.sort(key=operator.itemgetter('age'))
cedbeu
источник
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друзей.
Кевин
65
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.

pjz
источник
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']))

Он довольно хакерский, так как он основан на преобразовании значений в одно строковое представление для сравнения, но он работает, как и ожидалось, для чисел, в том числе и отрицательных (хотя вам нужно будет правильно отформатировать строку с нулевыми дополнениями, если вы используете числа)

Dologan
источник
2
отсортированный с помощью timsort, который является стабильным, вы можете вызвать сортировку несколько раз, чтобы выполнить сортировку по нескольким критериям
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» каждого элемента.

efotinis
источник
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
источник
21

Я полагаю, вы имели в виду:

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

Это будет отсортировано так:

sorted(l,cmp=lambda x,y: cmp(x['name'],y['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 Schwartzian

В информатике преобразование Шварца - это идиома программирования на Perl, используемая для повышения эффективности сортировки списка элементов. Эта идиома подходит для сортировки на основе сравнения, когда упорядочение фактически основано на упорядочении определенного свойства (ключа) элементов, где вычисление этого свойства является интенсивной операцией, которая должна выполняться минимальное количество раз. Преобразование Шварца отличается тем, что оно не использует именованные временные массивы.

kiriloff
источник
9
Python поддерживает key=for .sortс 2.4, то есть с 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}]
uingtea
источник
11

Вот альтернативное общее решение - оно сортирует элементы dict по ключам и значениям. Преимущество этого - нет необходимости указывать ключи, и оно все равно будет работать, если в некоторых словарях отсутствуют некоторые ключи.

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
источник
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()

Вот некоторые эталонные значения для крошечного списка и большого списка (более 100 тыс.):

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 для больших панд: вы указываете «setup_small», где он должен быть «setup_large». Изменение этого аргумента привело к тому, что программа запустилась без завершения, и я остановил ее более чем через 5 минут. Когда я запустил «timeit (1)», Панды Большого Метода закончили за 7,3 секунды, что намного хуже, чем LC или LC2.
clp2
Вы совершенно правы, это было довольно упущением с моей стороны. Я больше не рекомендую это для больших случаев! Я отредактировал ответ, чтобы просто допустить его как возможность, вариант использования все еще обсуждается.
Эбби Собх
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()функцию, передающую ему функцию listand и key, затем присвойте возвращенный отсортированный 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}]
Srisaila
источник
6

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

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

Проверьте это .

Shank_Transformer
источник
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

Быстрое сравнение времени исполнения

# 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 мксек на петлю

Bejür
источник
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

Оба метода сортируют список в одном и том же порядке (проверяется выполнением последнего оператора в блоке кода), но один немного быстрее.

SWAC
источник
-1

Вы можете использовать следующий код

sorted_dct = sorted(dct_name.items(), key = lambda x : x[1])
Loochie
источник