Python: сортировать массив словарей с пользовательским компаратором?

У меня есть следующий набор словарей Python:

myarr = [ { 'name': 'Richard', 'rank': 1 },
{ 'name': 'Reuben', 'rank': 4 },
{ 'name': 'Reece', 'rank': 0 },
{ 'name': 'Rohan', 'rank': 3 },
{ 'name': 'Ralph', 'rank': 2 },
{ 'name': 'Raphael', 'rank': 0 },
{ 'name': 'Robin', 'rank': 0 } ]

Я хотел бы отсортировать его по значениям ранга, упорядочиваясь следующим образом: 1-2-3-4-0-0-0.

Если я попробую:

sorted_master_list = sorted(myarr, key=itemgetter('rank'))

то список сортируется в порядке 0-0-0-1-2-3-4.

Как я могу определить пользовательскую функцию компаратора для толкания нулей в нижней части списка? Мне интересно, могу ли я использовать что-то вроде methodcaller.

Ответ 1

Вариант 1:

key=lambda d:(d['rank']==0, d['rank'])

Вариант 2:

key=lambda d:d['rank'] if d['rank']!=0 else float('inf')

Демо:

"Я бы хотел сортировать его по значениям ранга, упорядочиваясь следующим образом: 1-2-3-4-0-0-0". --Оригинальный плакат

>>> sorted([0,0,0,1,2,3,4], key=lambda x:(x==0, x))
[1, 2, 3, 4, 0, 0]

>>> sorted([0,0,0,1,2,3,4], key=lambda x:x if x!=0 else float('inf'))
[1, 2, 3, 4, 0, 0]

 

Дополнительные комментарии:

"Пожалуйста, не могли бы вы объяснить мне (новичок Python), что он делает? Я вижу, что это лямбда, которую я знаю, анонимная функция: какой бит в скобках?" - Комментарий к проекту

Обозначение индексирования/среза:

itemgetter('rank') - это то же самое, что и lambda x: x['rank'] - это то же самое, что и функция:

def getRank(myDict):
    return myDict['rank']

[...] называется обозначением индексирования/среза, см. Объяснить нотацию фрагмента Python - Также обратите внимание, что someArray[n] является общей нотацией на многих языках программирования для индексирования, но может не поддерживать срезы формы [start:end] или [start:end:step].

key= vs cmp= против богатого сравнения:

Что касается того, что происходит, есть два общих способа указать, как работает алгоритм сортировки: один с функцией key, а другой - с функцией cmp (теперь устаревший в python, но много более универсальный). В то время как функция cmp позволяет произвольно указать, как должны сравниваться два элемента (ввод: a, b; output: a<b или a>b или a==b). Несмотря на то, что это законно, это не дает нам большой пользы (нам пришлось бы дублировать код неловко), а ключевая функция более естественна для вашего дела. (См. "Сравнение с богатым объектами" для того, как неявно определять cmp= элегантным, но, возможно, чрезмерным образом.)

Реализация ключевой функции:

К сожалению, 0 является элементом целых чисел и, следовательно, имеет естественный порядок: 0 обычно < 1,2,3... Таким образом, если мы хотим наложить дополнительное правило, нам нужно отсортировать список на "более высоком уровне". Мы делаем это, заставляя ключ кортежем: кортежи сортируются сначала по их 1-му элементу, затем по их 2-му элементу. Истина всегда будет упорядочена после Ложь, поэтому все Trues будут заказаны после Falses; они будут сортироваться как обычно: (True,1)<(True,2)<(True,3)<..., (False,1)<(False,2)<..., (False,*)<(True,*). Альтернатива (вариант 2) просто присваивает словарям-0 словаря значение бесконечности, так как это гарантировано выше любого возможного ранга.

Более общая альтернатива - сравнение с богатыми объектами:

Еще более общим решением было бы создать класс, представляющий записи, а затем реализовать __lt__, __gt__, __eq__, __ne__, __gt__, __ge__, а все остальные богатые операторы сравнения или, наоборот, просто реализовать один из них и __eq__ и использовать @functools.total_ordering декоратор. Это заставит объекты этого класса использовать пользовательскую логику всякий раз, когда вы используете операторы сравнения (например, x=Record(name='Joe', rank=12) y=Record(...) x<y); поскольку функция sorted(...) использует < и другие операторы сравнения по умолчанию в сортировке, это автоматически сделает поведение при сортировке, а в других случаях, когда вы используете < и другие операторы сравнения. Это может быть или не быть чрезмерным в зависимости от вашего варианта использования.

Чистая альтернатива - не перегружать 0 семантикой:

Однако я должен отметить, что это немного искусственно, чтобы поставить 0s за 1,2,3,4 и т.д. Является ли это оправданным, зависит от того, действительно ли rank = 0 означает rank = 0; если rank = 0 действительно "ниже", чем rank = 1 (которые, в свою очередь, действительно "ниже", чем rank = 2...). Если это действительно так, то ваш метод отлично подходит. Если это не так, то вы можете исключить запись 'rank':..., а не устанавливать 'rank':0. Затем вы можете сортировать Лев Левицкий с помощью 'rank' in d или по:

Вариант 1 с другой схемой:

key=lambda d: (not 'rank' in d, d['rank'])

Вариант 2 с другой схемой:

key=lambda d: d.get('rank', float('inf'))

sidenote: Опираясь на существование бесконечности в python, почти граничит с хаком, делая любое из упомянутых решений (кортежи, сравнение объектов), Lev filter-then-concatenate solution и даже, возможно, немного более сложное cmp решение (напечатано wilson), более обобщенное для других языков.

Ответ 2

Я бы сделал

 sortedlist = sorted([x for x in myarr if x['rank']], key=lambda x: x['rank']) + [x for x in myarr if not x['rank']]

Я думаю, что он может быть каким-то образом сжат.

Ответ 3

Я больше склоняюсь к созданию функции сравнения для обработки "0":

def compare(x,y):
    if x == y:
        return 0
    elif x == 0:
        return 1
    elif y == 0:
        return -1
    else:
        return cmp(x,y)

sorted(myarr, cmp=lambda x,y: compare(x,y), key=lambda x:x['rank'])

Однако в пользовательской функции сравнения есть ограничение производительности.

Ответ 4

Хакерный способ сделать это:

sorted_master_list = sorted(myarr, key=lambda x: 99999 if x['rank'] == 0 else x['rank'])

Это работает достаточно хорошо, если вы знаете свой максимальный ранг.

Ответ 5

Связывание myarr здесь не похоже на действительный код Python (и не выполняется в моем сеансе интерпретатора.

Оказание этого в:

myarr = {
    'Richard': 1,
    'Reuben': 4,
    'Reece': 0,
    'Rohan': 3,
    'Ralph': 2,
    'Raphael': 0,
    'Robin': 0 }

Дает мне что-то, на чем я мог бы ответить.

Рекомендуемым способом создания пользовательской сортировки в Python является использование шаблона DSU (украшение, сортировка, undecorate). Если вы хотите отсортировать словарь по значениям, то это выглядит примерно так:

keys_sorted_by_val = [ x[1] for x in sorted([(v,k) for k,v in myarr.items()])]

... где (v,k) for k,v in myarr.items() - выражение украшать; sorted() - это, очевидно, sort, а внешний x[1] for x in ... - последний шаг undecorate.

Очевидно, это может показаться достаточно распространенным требованием, которое можно было бы обернуть в функцию:

def dict_by_values(d):
    return [ x[1] for x in sorted([(v,k) for k,v in d.items()])]

Если у вас есть коллекция экземпляров объектов, которые вы хотите отсортировать по некоторому атрибуту, вы можете использовать что-то вроде этого:

def sort_by_attr(attr, coll):
    results = list()
    for each in coll:
        assert hasattr(each, attr)
        results.append((getattr(each, attr), each))
    results.sort()
    return [x[1] for x in results]

Итак, если мы создали класс, представляющий ваши данные имени/ранга, например:

class NameRanking(object):
    def __init__(self, name, rank):
        self.name = name
        self.rank = rank
    def __repr__(self):
        return "%s: %s, %s" %(self.__class__, self.name, self.rank)

... и создайте список тех, кто использует myarr:

name_rankings = [NameRanking (k, v) для k, v в myarr.items()]

... тогда мы могли бы получить отсортированную копию, используя:

names_rankings_by_rank = sort_by_attr('rank', name_rankings)

(Да assert здесь не очень хорошая идея: там, где вы бы поместили свою собственную обработку исключений или код бросания в соответствии с вашим приложением).

Ответ 6

Просто передайте "ключ" произвольной функции или вызываемому объекту - это то, что нужно. itemgetter оказывается одной из таких функций, но она может работать с любой функцией, которую вы пишете - она ​​просто должна принимать один параметр как входной сигнал и возвращать объект, который непосредственно компилируется для достижения желаемого порядка.

В этом случае:

def key_func(item):
   return item["rank"] if item["rank"] != 0 else -100000

sorted_master_list = sorted(myarr, key=key_func)

(его также можно записать в виде лямбда-выражения)

Ответ 7

Вы можете использовать функцию в ключевом параметре:

для сортировки задницы:

sorted_master_list = sorted(myarr, key=lambda x: x.get('rank'))

или desc:

sorted_master_list = sorted(myarr, key=lambda x: -x.get('rank'))

Также вы можете прочитать о отсортированной функции здесь http://wiki.python.org/moin/HowTo/Sorting

Ответ 8

попробовать sorted_master_list = sorted (myarr, key = itemgetter ('rank'), reverse = True)