Сортировка словаря с помощью 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
на пару вложенных ключей.
Ответы
Ответ 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