Slicing a Python OrderedDict

后端 未结 6 1215
别那么骄傲
别那么骄傲 2020-12-15 19:48

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 Typ

相关标签:
6条回答
  • 2020-12-15 20:02

    You are dealing with a double-ended queue or deque, which is a classic data structure. Here is a link: https://www.geeksforgeeks.org/deque-in-python/

    0 讨论(0)
  • 2020-12-15 20:03

    I wanted to slice using a key, since I didn't know the index in advance:

    o = OrderedDict(zip(list('abcdefghijklmnopqrstuvwxyz'),range(1,27)))
    
    stop = o.keys().index('e')           # -> 4
    OrderedDict(islice(o.items(),stop))  # -> OrderedDict([('a', 1), ('b', 2), ('c', 3)])
    

    or to slice from start to stop:

    start = o.keys().index('c')                    # -> 2
    stop = o.keys().index('e')                     # -> 4
    OrderedDict(islice(o.iteritems(),start,stop))  # -> OrderedDict([('c', 3), ('d', 4)])
    
    0 讨论(0)
  • 2020-12-15 20:06

    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]
    
    0 讨论(0)
  • 2020-12-15 20:07

    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
    
    0 讨论(0)
  • 2020-12-15 20:10

    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)
    
    0 讨论(0)
  • 2020-12-15 20:13
    def slice_odict(odict, start=None, end=None):
        return OrderedDict([
            (k,v) for (k,v) in odict.items() 
            if k in list(odict.keys())[start:end]
        ])
    

    This allows for:

    >>> x = OrderedDict([('a',1), ('b',2), ('c',3), ('d',4)])
    >>> slice_odict(x, start=-1)
    OrderedDict([('d', 4)])
    >>> slice_odict(x, end=-1)
    OrderedDict([('a', 1), ('b', 2), ('c', 3)])
    >>> slice_odict(x, start=1, end=3)
    OrderedDict([('b', 2), ('c', 3)])
    
    0 讨论(0)
提交回复
热议问题