Сортировать список диктов по ключу с разными типами в Python 3 - PullRequest
0 голосов
/ 01 июля 2018

У меня есть метод, который группирует список dict по ключу. Чтобы сделать это, я обнаружил здесь , что мне нужно использовать функцию groupby, но перед тем, как сортировать список. Вот мой метод прямо сейчас:

def group_list_by_key(data, key):
    data.sort(key=lambda x: x[key])
    result = []
    for k, v in groupby(data, key=lambda x: x[key]):
        result.append(list(v))
    return result

Этот фрагмент кода работает, только если каждый ключ определен во всех диктовках, а значения имеют одинаковый тип. Однако, где я использую этот метод, я не знаю, везде ли определены ключи и имеют ли они один и тот же тип. В Python 2.x я знаю, что существует функция sorted с параметром cmp, которая может выполнять пользовательскую сортировку, но из Python 3.x это больше невозможно. Есть ли способ сделать заказную сортировку? Я думаю об использовании классической сортировки по < и сортировке по typename.

К настоящему времени я подумал об использовании функции get и приведении к строке в виде, подобном

data.sort(key=lambda x: str(x.get(key)))
...
for k, v in groupby(data, key=lambda x: x.get(key)):

Преодолевает только строковое, числовое и None-содержимое, но не универсальный объект, и легко ломается, если, например, я выполню

a = [{'b': 0, 'c': 1}, {'b': '0'}, {'b': 0, 'c': 2}, {'b': 1}, {'c': 3}]
group_list_by_key(a, 'b')

Выход

[[{'b': 0, 'c': 1}], [{'b': '0'}], [{'b': 0, 'c': 2}], [{'b': 1}], [{'c': 3}]]

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

[[{'b': 0, 'c': 1}, {'b': 0, 'c': 2}], [{'b': '0'}], [{'b': 1}], [{'c': 3}]]

Ответы [ 2 ]

0 голосов
/ 02 июля 2018

Спасибо @Sunitha и @ njzk2 за указание на функцию cmp_to_key , она полностью сделала то, что я хотел. Итак, моя группировка сейчас:

from functools import cmp_to_key
from itertools import groupby

def group_list_by_key(data, key):
    def compare_values_types(a, b):
        a = a.get(key)
        b = b.get(key)
        if a.__class__ == b.__class__:
            if a < b:
                return -1
            elif a > b:
                return 1
            else:
                return 0
        else:
            if a.__class__.__name__ < b.__class__.__name__:
                return -1
            elif a.__class__.__name__ > b.__class__.__name__:
                return 1
            else:
                return 0
    data.sort(key=cmp_to_key(compare_values_types))
    return [list(v) for k, v in groupby(data, key=lambda x: x.get(key))]

Вызов списка образцов

a = [{'b': 0, 'c': 1}, {'b': '0'}, {'b': 0, 'c': 2}, {'b': 1}, {'c': 3}]
group_list_by_key(a, 'b')

Возвращает ожидаемый список

[[{'c': 3}], [{'b': 0, 'c': 1}, {'b': 0, 'c': 2}], [{'b': 1}], [{'b': '0'}]]

То, что я сделал, это сравнил классическим способом ключи одного и того же типа, в противном случае я просто делаю сравнение строк между именами классов (используя a.__class__.__name__ вместо type(a).__name__, посмотрите на этот ответ ). Спасибо всем!

0 голосов
/ 01 июля 2018

Вы можете решить свою проблему, сделав что-то вроде этого

data = [{'b': 0, 'c': 1}, {'b': '0'}, {'b': 0, 'c': 2}, {'b': 1}, {'c': 3}]
key='b'

def f(x):
     ret = x.get(key, -1)
     return ret if type(ret) == int else -2

result = [list(v) for k, v in groupby(sorted(data, key=f), f)]

# result: [[{'b': '0'}], [{'c': 3}], [{'b': 0, 'c': 1}, {'b': 0, 'c': 2}], [{'b': 1}]]

Но если вам все еще нужна пользовательская функция сравнения, вы можете сделать это, используя functools.cmp_to_key

import functools
sorted(x, key=functools.cmp_to_key(custom_cmp_function))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...