Search code examples
pythonclassinstance

Why can't my version of a sorted list in Python add a second number?


In the following code, I'm able to add the first number, but I can't add the second number. Inside the class, self correctly updates to [1, 3], but the instance stays at [1]. Why is that and how do I fix it?

from bisect import bisect
class SortedList(list):
    def __init__(self):
        self = []
    def add(self, x):
        index = bisect(self, x)
        if not self:
            self.append(x)
        else:
            self = self + [x] + self[index:] # after the second call self = [1, 3]
            pass
t = 1
for _ in range(t):
    n, a, b = 24, 3, 5
    if b == 1:
        print('yes')
    else:
        sl = SortedList() # sl = []
        st = set()
        sl.add(1) # sl = [1]
        st.add(1)
        i = 0
        while sl[i] < n: # when i == 1, there's an error because sl = [1]
            num1 = sl[i] * a # num1 = 3
            num2 = sl[i] + b
            if num1 > sl[i] and num1 not in st:
                sl.add(num1) # after this, sl = [1] still
                st.add(num1)
            if num2 > 1 and num2 not in st:
                sl.add(num2)
                st.add(num2)
            if n in st:
                break
            i += 1
        print('yes' if n in st else 'no')

Solution

  • Don't modify self, when you assign the resulting list to self you change its reference in the local context of the function. But the remote reference keep unchanged. Better explained in Is it safe to replace a self object by another object of the same type in a method?

    By sub-classing list:

    import bisect
    
    class SortedList(list):
    
        def append(self, x):
            if not self:
                super(SortedList, self).append(x)
            else:
                idx = bisect.bisect(self, x)
                self.insert(idx, x)
    

    And then:

    >>> import random
    >>> l = SortedList()
    >>> for i in range(10):
    ...     l.append(random.randint(0, 100))
    >>> print(l)
    [5, 31, 50, 58, 69, 69, 70, 78, 85, 99]
    

    In order to keep the list sorted you should also override some magic methods such __add__, __iadd__ ...

    By wrapping list:

    class SortedList(object):
    
        def __init__(self):
            self._data = []
    
        def append(self, x):
            if not self:
                self._data = [x]
            else:
                idx = bisect.bisect(self, x)
                # Here you can change self._data
                # Even `insert` is better, because it don't need to copy the list
                # you can do
                self._data = self._data[:idx] + [x] + self._data[idx:]
    

    But it's very partial, in order to have SortedList look like a list you have to implement the sequence protocol.