2

I have list which is already sorted.

I often need to check to see

if foo in sortedlist:
    pass  # not really.  but not the point.

Is there a way to teach 'in' that sortedlist is sorted and that it should binary search the list?

Good Person
  • 1,437
  • 2
  • 23
  • 42

2 Answers2

4

Python favours explicit over implicit. Your options are to explicitly use the bisect module if you know your data is sorted, or create a subclass of list that implements __contains__ by using that module.

For example:

import bisect


class SortedList(list):
    def __contains__(self, elem):
        idx = bisect.bisect_left(self, elem)
        return idx < len(self) and self[idx] == elem

could be used as a substitute for list, and in will use __contains__ automatically. You'd probably want to override __setitem__, .extend() and .append() to maintain the list in sorted order, too.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • @MartijnPieters: your code is not correct: It raise a `IndexError: list index out of range` if elem is larger than the largest element of the list: the returned `idx` is `len(self)`. – hivert May 10 '14 at 06:55
  • @MartijnPieters not on my machine... You should definitely check on yours. – hivert May 10 '14 at 07:06
  • @hivert: ah, no, I was indeed forgetting the `elem > self[-1]` case. Thanks for pointing that out. Corrected. – Martijn Pieters May 10 '14 at 07:06
2

My suggestion would be to subclass list to use sorted list:

from bisect import bisect_left
class SortedList(list):
    def __init__(self, l):
        list.__init__(self, sorted(l))
    def __contains__(self, obj):
        pos = bisect_left(self, obj, 0, len(self))
        return (pos != len(self) and self[pos] == obj)

Then:

>>> l = SortedList([4,3,5435,123,54,2,343,23])
>>> l
[2, 3, 4, 23, 54, 123, 343, 5435]
>>> 23 in l
True
>>> 25 in l
False
>>> 123122 in l
False
>>> -1 in l
False
hivert
  • 10,579
  • 3
  • 31
  • 56