69

I have od of type OrderedDict. I want to access its most recently added (key, value) pair. od.popitem(last = True) would do it, but would also remove the pair from od which I don't want.

What's a good way to do that? Can /should I do this:

class MyOrderedDict(OrderedDict):
  def last(self):
    return next(reversed(self))
max
  • 49,282
  • 56
  • 208
  • 355

4 Answers4

105

Using next(reversed(od)) is a perfect way of accessing the most-recently added element. The class OrderedDict uses a doubly linked list for the dictionary items and implements __reversed__(), so this implementation gives you O(1) access to the desired element. Whether it is worthwhile to subclass OrderedDict() for this simple operation may be questioned, but there's nothing actually wrong with this approach.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • But that only returns the key of the last item in the OD, right? not the entire item (key, value). `next(reversed(OrderedDict([(0,'a'),(1,'b'),(2,'c')])))` gives `2` not `(2, 'c')` – hobs Apr 05 '13 at 01:50
  • 24
    @hobs: Yes, it only gives you the key. How to get the value given the key is left as an exercise to the reader. :) – Sven Marnach Apr 05 '13 at 12:18
  • 5
    FWIW, O(2) and O(1) are asymptotically equivalent – matt Jan 29 '14 at 05:58
  • 9
    In Python 3.5, you can do next(reversed(od.values())) to get the last value – UXkQEZ7 Feb 29 '16 at 02:43
  • 8
    Also note that if you want the *first* element you should use `next(iter(od))` – BlackBear Mar 30 '16 at 15:24
  • Just a clarification: From line 82 of collections.py (with the Python 2.7.8 source, not sure about newer 2.7 versions) it appears that the internal linked list is actually a cycle. At first this answer did not make sense to me, because the first node of a doubly linked list need not have the final node as a "predecessor" node. It could have a "null" node, and the final node might similarly have a "null" node as its successor. But the OrderedDict implementation cycles back, so the O(1) access comes from the ability to get the final node by a simple array access from the root node's data. – ely Apr 09 '16 at 02:18
  • 3
    No wonder that in Python 3.5 you can do `next(reversed(od.items()))` to get the last `(key, value)` pair. – mmj May 31 '16 at 10:41
  • How to get last n elements from OrderedDict? – Artur Jun 20 '22 at 22:17
  • 1
    @Artur You can use `itertools.islice()` on `reversed(od)`. – Sven Marnach Jun 21 '22 at 08:16
24

God, I wish this was all built-in functionality...

Here's something to save you precious time. Tested in Python 3.7. od is your OrderedDict.


# Get first key
next(iter(od))

# Get last key
next(reversed(od))

# Get first value
od[next(iter(od))]

# Get last value
od[next(reversed(od))]

# Get first key-value tuple
next(iter(od.items()))

# Get last key-value tuple
next(reversed(od.items()))
Hubert Grzeskowiak
  • 15,137
  • 5
  • 57
  • 74
18

A little magic from timeit can help here...

from collections import OrderedDict
class MyOrderedDict1(OrderedDict):
  def last(self):
    k=next(reversed(self))
    return (k,self[k])

class MyOrderedDict2(OrderedDict):
  def last(self):
     out=self.popitem()
     self[out[0]]=out[1]
     return out

class MyOrderedDict3(OrderedDict):
  def last(self):
     k=(list(self.keys()))[-1]
     return (k,self[k])

if __name__ == "__main__":
  from timeit import Timer

  N=100

  d1=MyOrderedDict1()
  for i in range(N): d1[i]=i

  print ("d1",d1.last())

  d2=MyOrderedDict2()
  for i in range(N): d2[i]=i

  print ("d2",d2.last())

  d3=MyOrderedDict3()
  for i in range(N): d3[i]=i

  print("d3",d3.last())



  t=Timer("d1.last()",'from __main__ import d1')
  print ("OrderedDict1",t.timeit())
  t=Timer("d2.last()",'from __main__ import d2')
  print ("OrderedDict2",t.timeit())
  t=Timer("d3.last()",'from __main__ import d3')
  print ("OrderedDict3",t.timeit())

results in:

d1 (99, 99)
d2 (99, 99)
d3 (99, 99)
OrderedDict1 1.159217119216919
OrderedDict2 3.3667118549346924
OrderedDict3 24.030261993408203

(Tested on python3.2, Ubuntu Linux).

As pointed out by @SvenMarnach, the method you described is quite efficient compared to the other two ways I could cook up.

mgilson
  • 300,191
  • 65
  • 633
  • 696
2

Your idea is fine, however the default iterator is only over the keys, so your example will only return the last key. What you actually want is:

class MyOrderedDict(OrderedDict):
    def last(self):
        return list(self.items())[-1]

This gives the (key, value) pairs, not just the keys, as you wanted.

Note that on pre-3.x versions of Python, OrderedDict.items() returns a list, so you don't need the list() call, but later versions return a dictionary view object, so you will.

Edit: As noted in the comments, the quicker operation is to do:

class MyOrderedDict(OrderedDict):
    def last(self):
        key = next(reversed(self))
        return (key, self[key])

Although I must admit I do find this uglier in the code (I never liked getting the key then doing x[key] to get the value separately, I prefer getting the (key, value) tuple) - depending on the importance of speed and your preferences, you may wish to pick the former option.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • 3
    This will be an O(n) operation, of course. It would be better to use the original implementation to get the key, and get the value by normal dictionary access. (And when you anyway have to buld a list, `[-1]` is much easier than `next(reversed(...))`.) – Sven Marnach Mar 28 '12 at 23:55
  • You make a good point, corrected. Too easy to get locked into one way of solving a problem. – Gareth Latty Mar 28 '12 at 23:59