Нарезка словаря
У меня есть словарь, и я хотел бы передать часть его функции, причем эта часть задается списком (или кортежем) ключей. Вот так:
# the dictionary
d = {1:2, 3:4, 5:6, 7:8}
# the subset of keys I'm interested in
l = (1,5)
Теперь, в идеале, я хотел бы иметь возможность сделать это:
>>> d[l]
{1:2, 5:6}
... но это не работает, так как он будет искать ключ с именем (1,5)
. И d[1,5]
даже не действителен Python (хотя, похоже, это будет удобно).
Я знаю, что могу сделать это:
>>> dict([(key, value) for key,value in d.iteritems() if key in l])
{1: 2, 5: 6}
или это:
>>> dict([(key, d[key]) for key in l])
который более компактен... но я считаю, что должен быть "лучший" способ сделать это. Не хватает ли более элегантного решения?
(Я использую Python 2.7)
Ответы
Ответ 1
Вы должны выполнять итерацию по кортежу и проверять, находится ли ключ в dict, а не наоборот, если вы не проверяете, существует ли ключ и нет в dict, вы получите ключевую ошибку:
print({k:d[k] for k in l if k in d})
Некоторые тайминги:
{k:d[k] for k in set(d).intersection(l)}
In [22]: %%timeit
l = xrange(100000)
{k:d[k] for k in l}
....:
100 loops, best of 3: 11.5 ms per loop
In [23]: %%timeit
l = xrange(100000)
{k:d[k] for k in set(d).intersection(l)}
....:
10 loops, best of 3: 20.4 ms per loop
In [24]: %%timeit
l = xrange(100000)
l = set(l)
{key: d[key] for key in d.viewkeys() & l}
....:
10 loops, best of 3: 24.7 ms per
In [25]: %%timeit
l = xrange(100000)
{k:d[k] for k in l if k in d}
....:
100 loops, best of 3: 17.9 ms per loop
Я не вижу, как {k:d[k] for k in l}
не читается или изящно, и если все элементы находятся в d, то это довольно эффективно.
Ответ 2
Используйте набор для пересечения в представлении слова dict.viewkeys()
:
l = {1, 5}
{key: d[key] for key in d.viewkeys() & l}
Это синтаксис Python 2, в Python 3 используется d.keys()
.
Это все еще использует цикл, но, по крайней мере, понимание словаря намного читаемо. Использование множества пересечений очень эффективно, даже если d
или l
велико.
Демо-версия:
>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = {1, 5}
>>> {key: d[key] for key in d.viewkeys() & l}
{1: 2, 5: 6}
Ответ 3
В Python 3 вы можете использовать itertools islice
для dict.items()
итератора dict.items()
import itertools
d = {1: 2, 3: 4, 5: 6}
dict(itertools.islice(d.items(), 2))
{1: 2, 3: 4}
Примечание: это решение не учитывает конкретные ключи. Он разрезает по внутреннему порядку d
, что в Python 3. 7+ гарантированно будет упорядоченным по вставке.
Ответ 4
Написать dict
подкласс, который принимает список ключей в качестве "элемента" и возвращает "срез" словаря:
class SliceableDict(dict):
default = None
def __getitem__(self, key):
if isinstance(key, list): # use one return statement below
# uses default value if a key does not exist
return {k: self.get(k, self.default) for k in key}
# raises KeyError if a key does not exist
return {k: self[k] for k in key}
# omits key if it does not exist
return {k: self[k] for k in key if k in self}
return dict.get(self, key)
Применение:
d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d[[1, 5]] # {1: 2, 5: 6}
Или, если вы хотите использовать отдельный метод для этого типа доступа, вы можете использовать *
для принятия любого количества аргументов:
class SliceableDict(dict):
def slice(self, *keys):
return {k: self[k] for k in keys}
# or one of the others from the first example
d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d.slice(1, 5) # {1: 2, 5: 6}
keys = 1, 5
d.slice(*keys) # same
Ответ 5
set intersection
и dict comprehension
можно использовать здесь
# the dictionary
d = {1:2, 3:4, 5:6, 7:8}
# the subset of keys I'm interested in
l = (1,5)
>>>{key:d[key] for key in set(l) & set(d)}
{1: 2, 5: 6}