Slicing a dictionary

Question:

I have a dictionary, and would like to pass a part of it to a function, that part being given by a list (or tuple) of keys. Like so:

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

Now, ideally I’d like to be able to do this:

>>> d[l]
{1:2, 5:6}

… but that’s not working, since it will look for a key matching the tuple (1,5), the same as d[1,5].

d{1,5} isn’t even valid Python (as far as I can tell …), though it might be handy: The curly braces suggest an unordered set or a dictionary, so returning a dictionary containing the specified keys would look very plausible to me.

d[{1,5}] would also make sense ("here’s a set of keys, give me the matching items"), and {1, 5} is an unhashable set, so there can’t be a key that matches it — but of course it throws an error, too.

I know I can do this:

>>> dict([(key, value) for key,value in d.iteritems() if key in l])
{1: 2, 5: 6}

or this:

>>> dict([(key, d[key]) for key in l])

which is more compact
… but I feel there must be a "better" way of doing this. Am I missing a more elegant solution?

(I’m using Python 2.7)

Asked By: Zak

||

Answers:

Use a set to intersect on the dict.viewkeys() dictionary view:

l = {1, 5}
{key: d[key] for key in d.viewkeys() & l}

This is Python 2 syntax, in Python 3 use d.keys().

This still uses a loop, but at least the dictionary comprehension is a lot more readable. Using set intersections is very efficient, even if d or l is large.

Demo:

>>> 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}
Answered By: Martijn Pieters

You should be iterating over the tuple and checking if the key is in the dict not the other way around, if you don’t check if the key exists and it is not in the dict you are going to get a key error:

print({k:d[k] for k in l if k in d})

Some timings:

 {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

I don’t see how {k:d[k] for k in l} is not readable or elegant and if all elements are in d then it is pretty efficient.

Answered By: Padraic Cunningham

Write a dict subclass that accepts a list of keys as an “item” and returns a “slice” of the dictionary:

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)

Usage:

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d[[1, 5]]   # {1: 2, 5: 6}

Or if you want to use a separate method for this type of access, you can use * to accept any number of arguments:

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
Answered By: kindall

set intersection and dict comprehension can be used here

# 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}
Answered By: itzMEonTV

On Python 3 you can use the itertools islice to slice the dict.items() iterator

import itertools

d = {1: 2, 3: 4, 5: 6}

dict(itertools.islice(d.items(), 2))

{1: 2, 3: 4}

Note: this solution does not take into account specific keys. It slices by internal ordering of d, which in Python 3.7+ is guaranteed to be insertion-ordered.

Answered By: Cesar Canassa

the dictionary

d = {1:2, 3:4, 5:6, 7:8}

the subset of keys I’m interested in

l = (1,5)

answer

{key: d[key] for key in l}
Answered By: diman Bond

To slice a dictionary, Convert it to a list of tuples using d.items(), slice the list and create a dictionary out of it.

Here.

d = {1:2, 3:4, 5:6, 7:8}

To get the first 2 items

first_two = dict(list(d.items())[:2])

first_two

{1: 2, 3: 4}

Answered By: sostom

Another option is to convert the dictionary into a pandas Series object and then locating the specified indexes:

>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = [1,5]

>>> import pandas as pd
>>> pd.Series(d).loc[l].to_dict()
{1: 2, 5: 6}
Answered By: Ivan De Paz Centeno

My case is probably relatively uncommon, but, I’m posting it here nonetheless in case it helps someone (though not OP directly).

I came across this question searching how to slice a dictionary that had item counts. Basically I had a dictionary where the keys were letters, and the values were the number of times the letter appeared (i.e. abababc –> {'a': 3, 'b': 3, 'c': 1} I wanted to ‘slice’ the dictionary so that I could return the most common n keys.

It turns out that this is exactly what a Collections Counter object is for, and instead of needing to ‘slice’ my dictionary, I could easily just convert it to a collections.Counter and then call most_common(n): https://docs.python.org/3/library/collections.html#collections.Counter.most_common

Answered By: Raleigh L.

You can do slicing of the dictionary with the help of the module dictionarify
This is the link for documentation-https://github.com/suryavenom/Flexi/blob/main/README.md.
Installation –
pip install dictionarify

Answered By: surya github

Slicing a dict using numpy

d = {'a':1, 'b': 2, 'c': 3, 'd': 4}

{*np.array([*d])[[1,-2]]}   # {'b', 'c'}

Various slice types are supported, see https://numpy.org/doc/stable/user/basics.indexing.html.

With operator.itemgetter:

dict(zip(l, itemgetter(*l)(d)))

Try it online!

Answered By: Kelly Bundy
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.