Подтвердить что ты не робот

Сортировка словаря с помощью operator.itemgetter

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

Я только что прочитал о методе operator.itemgetter сортировки несколько дней назад и решил попробовать, но он, похоже, не работает.

Не то, чтобы у меня были проблемы с ответами на вопросы, я просто хотел попробовать это с помощью operator.itemgetter.

Таким образом, dict был:

>>> mydict = { 'a1': ['g',6],
           'a2': ['e',2],
           'a3': ['h',3],
           'a4': ['s',2],
           'a5': ['j',9],
           'a6': ['y',7] }

Я пробовал это:

>>> l = sorted(mydict.itervalues(), key=operator.itemgetter(1))
>>> l
[['e', 2], ['s', 2], ['h', 3], ['g', 6], ['y', 7], ['j', 9]]

И это работает так, как я хочу. Однако, поскольку у меня нет полного словаря (mydict.itervalues()), я пробовал это:

>>> complete = sorted(mydict.iteritems(), key=operator.itemgetter(2))

Это не работает (как я и ожидал).

Итак, как мне сортировать dict с помощью operator.itemgetter и вызывать itemgetter на пару вложенных ключей.

4b9b3361

Ответ 1

In [6]: sorted(mydict.iteritems(), key=lambda (k,v): operator.itemgetter(1)(v))
Out[6]: 
[('a2', ['e', 2]),
 ('a4', ['s', 2]),
 ('a3', ['h', 3]),
 ('a1', ['g', 6]),
 ('a6', ['y', 7]),
 ('a5', ['j', 9])]

Ключевым параметром всегда является функция, которая одновременно подает один элемент из итерируемого (mydict.iteritems()). В этом случае элемент может быть чем-то вроде

('a2',['e',2])

Итак, нам нужна функция, которая может принимать ('a2',['e',2]) как входной и возвращаемый 2.

lambda (k,v): ... - анонимная функция, которая принимает один аргумент - 2-кортеж - и распаковывает его в k и v. Поэтому, когда функция lambda применяется к нашему элементу, k будет 'a2', а v будет ['e',2].

lambda (k,v): operator.itemgetter(1)(v), примененный к нашему элементу, таким образом возвращает operator.itemgetter(1)(['e',2]), который "itemgets" второй элемент в ['e',2], который равен 2.

Обратите внимание, что lambda (k,v): operator.itemgetter(1)(v) не является хорошим способом кодирования на Python. Как указывает gnibbler, operator.itemgetter(1) пересчитывается для каждого элемента. Это неэффективно. Точка использования operator.itemgetter(1) заключается в создании функции, которая может применяться многократно. Вы не хотите повторно создавать функцию каждый раз. lambda (k,v): v[1] более читабельна и быстрее:

In [15]: %timeit sorted(mydict.iteritems(), key=lambda (k,v): v[1])
100000 loops, best of 3: 7.55 us per loop

In [16]: %timeit sorted(mydict.iteritems(), key=lambda (k,v): operator.itemgetter(1)(v))
100000 loops, best of 3: 11.2 us per loop

Ответ 2

Ответ - вы не можете. operator.itemgetter(i) возвращает вызываемый, который возвращает элемент i своего аргумента, то есть

f = operator.itemgetter(i)
f(d) == d[i]

он никогда не будет возвращаться, как d[i][j]. Если вы действительно хотите сделать это в чисто функциональном стиле, вы можете написать свою собственную функцию compose():

def compose(f, g):
    return lambda *args: f(g(*args))

и используйте

sorted(mydict.iteritems(), key=compose(operator.itemgetter(1),
                                       operator.itemgetter(1)))

Заметьте, что я не рекомендовал это делать:)

Ответ 3

itemgetter не поддерживает вложенность (хотя attrgetter делает)

вам нужно сгладить dict так:

sorted(([k]+v for k,v in mydict.iteritems()), key=itemgetter(2))

Ответ 4

Обычно индексирование a la kv[1][1] выполняется быстрее:

>>> from timeit import timeit
>>> setup = 'import operator; g = operator.itemgetter(1); '
>>> setup += 'd = {i: list(range(i+2)) for i in range(100)}'
>>> kwargs = {'setup': setup, 'number': 10000}

>>> timeit('sorted(d.items(), key=lambda kv: kv[1][1])', **kwargs)
0.5251589557155967

>>> timeit('sorted(d.items(), key=lambda kv: g(kv[1]))', **kwargs)
0.7175205536186695

>>> timeit('sorted(d.items(), key=lambda kv: g(kv)[1])', **kwargs)
0.7915238151326776

>>> timeit('sorted(d.items(), key=lambda kv: g(g(kv)))', **kwargs)
0.9781978335231543