-2

Suppose I want to write a function that reverses the words in a sentence without using the split function. Here is one possible solution:

def reverse_words_1(s):
    i, n, r = 0, len(s), []
    while i < n:
        while i < n and s[i] == ' ': i += 1
        if i == n: break
        p = i 
        while i < n and s[i] != ' ': i += 1
        # Instead of appending here and then reversing after the while
        # loop is done, we could r.insert(0, ..). But insert is much
        # slower than append/reverse because insert always requires that
        # each pointer in the list must be copied and moved. Whereas
        # append only requires copying if there isn't enough space for
        # the new element in the currently allocated memory block.
        # Helpful explanations:
        # https://stackoverflow.com/questions/7776938/python-insert-vs-append
        # https://bytes.com/topic/python/answers/34036-timing-difference-insert-vs-append-reverse
        r.append( s[p : i] )
    r.reverse()
    return ' '.join(r)

The comment in my code notes that insert is much slower than append/reverse. But my comment really only compares the actions taken by insert and append. My comment doesn't address the actions or speed of reverse.

So how does reverse work in CPython? My guess is that reverse is re-pointing the pointers in the list. Something like this:

def reverse(lst):
    l, r = 0, len(lst) - 1
    while l < r:
        lst[l], lst[r] = lst[r], lst[l]
        l += 1
        r -= 1

Is this roughly how CPython internally performs the reverse function?

If my guess about how reverse works is correct, then I guess it's much faster to re-point pointers than it is to copy and move pointers?

waynemystir
  • 323
  • 3
  • 11

1 Answers1

1

That's more or less how it works the code is in listobject.c. The code below reverses a slice but the reverse method calls this using the whole list.

/* Reverse a slice of a list in place, from lo up to (exclusive) hi. */
static void
reverse_slice(PyObject **lo, PyObject **hi)
{
    assert(lo && hi);

    --hi;
    while (lo < hi) {
        PyObject *t = *lo;
        *lo = *hi;
        *hi = t;
        ++lo;
        --hi;
    }
}
GWW
  • 43,129
  • 11
  • 115
  • 108
  • Thank you. So I guess it's a lot faster to re-point pointers than it is to copy and move pointers? If so, why? – waynemystir Aug 22 '19 at 17:38
  • Every time you insert an entry at the front of a list all of the pointers in the list have to be moved. As the list gets bigger insert will get progressively slower. Append will only require the pointers to be copied if the lists internal buffer is full and it needs to be resized. – GWW Aug 22 '19 at 17:42
  • Is it much faster to re-point pointers than it is to copy and move pointers? I should have put more emphasis that this is my key question. – waynemystir Aug 24 '19 at 14:20
  • The pointer swapping used in the reverse function is much faster because the complexity of the function is O(N / 2), while the complexity of moving the pointers is O(N^2). So for a list of 10,000 elements the reverse function only has to iterate over 5000 elements, while the move and insert has to loop over (1 + 2 + 3 + ... 10000) = 50005000 elements. The difference gets progressively larger as the lists get longer. – GWW Aug 24 '19 at 20:49
  • Thank you again. Why is the complexity of moving pointers O(N^2)? – waynemystir Aug 25 '19 at 15:42
  • I am not talking about the complexity of moving a pointer I am just talking about the naive implementation of loop that would move the pointers for each insertion. – GWW Aug 25 '19 at 17:51