Slicing a Python OrderedDict

11,246

Solution 1

The ordered dict in the standard library, doesn't provide that functionality. Even though libraries existed for a few years before collections.OrderedDict that have this functionality (and provide essentially a superset of OrderedDict): voidspace odict and ruamel.ordereddict (I am the author of the latter package, which is a reimplementation of odict in C):

from odict import OrderedDict as odict
p = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print p[1:3]

In ruamel.ordereddict you can relax the ordered input requirement (AFAIK you cannot ask derivative of dict if its keys are ordered (would be good addition to ruamel.ordereddict to recognise collection.OrderedDicts)):

from ruamel.ordereddict import ordereddict

q = ordereddict(o, relax=True)
print q[1:3]
r = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print r[1:3]

If you want (or have to) stay within the standard library you can sublass collections.OrderedDict's __getitem__:

class SlicableOrderedDict(OrderedDict):
    def __getitem__(self, k):
        if not isinstance(k, slice):
            return OrderedDict.__getitem__(self, k)
        x = SlicableOrderedDict()
        for idx, key in enumerate(self.keys()):
            if k.start <= idx < k.stop:
                x[key] = self[key]
        return x

s = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print s[1:3]

of course you could use Martijn's or Jimmy's shorter versions to get the actual slice that needs returning:

from itertools import islice
class SlicableOrderedDict(OrderedDict):
    def __getitem__(self, k):
        if not isinstance(k, slice):
            return OrderedDict.__getitem__(self, k)
        return SlicableOrderedDict(islice(self.viewitems(), k.start, k.stop))

t = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print t[1:3]

or if you just want smarten up all existing OrderedDicts without subclassing:

def get_item(self, k):
    if not isinstance(k, slice):
        return OrderedDict._old__getitem__(self, k)
    return OrderedDict(islice(self.viewitems(), k.start, k.stop))

OrderedDict._old__getitem__ = OrderedDict.__getitem__
OrderedDict.__getitem__ = get_item

u = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
print u[1:3]

Solution 2

You can use the itertools.islice function, which takes an iterable and outputs the stop first elements. This is beneficial since iterables don't support the common slicing method, and you won't need to create the whole items list from the OrderedDict.

from collections import OrderedDict
from itertools import islice
o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
sliced = islice(o.iteritems(), 3)  # o.iteritems() is o.items() in Python 3
sliced_o = OrderedDict(sliced)

Solution 3

In Python 2, you can slice the keys:

x.keys()[1:3]

and to support both Python 2 and Python 3, you'd convert to a list first:

list(k)[1:3]

The Python 2 OrderedDict.keys() implementation does exactly that.

In both cases you are given a list of keys in correct order. If creating a whole list first is an issue, you can use itertools.islice() and convert the iterable it produces to a list:

from itertools import islice

list(islice(x, 1, 3))

All of the above also can be applied to the items; use dict.viewitems() in Python 2 to get the same iteration behaviour as Python 3 dict.items() provides. You can pass the islice() object straight to another OrderedDict() in this case:

OrderedDict(islice(x.items(), 1, 3))  # x.viewitems() in Python 2

Solution 4

I was able to slice an OrderedDict using the following:

list(myordereddict.values())[start:stop]

I didn't test the performance.

Share:
11,246

Related videos on Youtube

Alice
Author by

Alice

Updated on September 15, 2022

Comments

  • Alice
    Alice over 1 year

    In my code I frequently need to take a subset range of keys+values from a Python OrderedDict (from collections package). Slicing doesn't work (throws TypeError: unhashable type) and the alternative, iterating, is cumbersome:

    from collections import OrderedDict
    
    o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
    
    # want to do:
    # x = o[1:3]
    # need to do:
    x = OrderedDict()
    for idx, key in enumerate(o):
        if 1 <= idx < 3:
            x[key] = o[key]
    

    Is there a better way to get this done?

  • Andy Hayden
    Andy Hayden over 7 years
    Is this O(n)? it seems like this operation needn't be.
  • Eric Lindauer
    Eric Lindauer over 4 years
    This is helpful, thanks! Note that the SlicableOrderedDict does not support negative indices as written.
  • amball
    amball over 3 years
    In Python 3.7, I get the error collections.OrderedDict' object has no attribute 'iteritems
  • lutuh
    lutuh over 3 years
    Use o.items() in Python 3.
  • diman82
    diman82 over 2 years
    I get an error: 'TypeError: unhashable type: 'slice' when using odict package
  • diman82
    diman82 over 2 years
    Also, changing the built-in getitem implementation (without subclassing) yields in 'TypeError: can't set attributes of built-in/extension type 'collections.OrderedDict' (for subclassing example change self.viewitems() to self.items() for python 3.x)
  • IgorZ
    IgorZ over 2 years
    This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
  • Jeremy Caney
    Jeremy Caney over 2 years
    @SureshKumarVeluswamy: Why not? It's short. It should include more explanation. It might even be redundant with one of the other six answers. But certainly it's an answer.