Python - insertion sort Series post - I am reading through Introduction to Algorithms, 3rd Edition implementing each algorithm as I read them.



Pretty basic insertion sort. Only considering numbers initially - how could I make this better and/or have better tests?

def insertion_sort(numbs):
    for cur_index in range(1, len(numbs)):
        cur_val = numbs[cur_index]

        lookup_index = cur_index - 1

        while lookup_index &gt;= 0 and numbs[lookup_index] &gt;= cur_val:
            numbs[lookup_index + 1] = numbs[lookup_index]
            lookup_index -= 1
        numbs[lookup_index + 1] = cur_val

    return numbs


def check_insertion_sort(input, expected):
    result = insertion_sort(input)
    if result != expected:
        print "Result={} does not equal expected={} for input={}".format(result, expected, input)
    else:
        print "Success for input={}".format(input)


check_insertion_sort([5, 2, 4, 6, 1, 3], [1, 2, 3, 4, 5, 6])
check_insertion_sort([190, 2, 4, 6, 1, 3], [1, 2, 3, 4, 6, 190])
check_insertion_sort([-190, 2, 4, 6, 1, 3], [-190, 1, 2, 3, 4, 6])
check_insertion_sort([0, 0, 4, 6, 1, 3], [0, 0, 1, 3, 4, 6])
check_insertion_sort([0, 0], [0, 0])
check_insertion_sort([1, 0], [0, 1])
First of all, awesome that you have written tests!

However, your checker is a bit verbose on the matching case. It is often considered good practice for automated tests to be as noise-free as possible. Consider the following outputs:

Success for input=...testcase...
... 300 lines of success ...
Success for input=...testcase...
Result=... does not equal expected=... for input=...
Success for input=...testcase...
... 300 lines of success ...
Success for input=...testcase...


Ok, you have written just a few testcases, but I think you see my point: you'll miss the errors. Better would be just

Result=... does not equal expected=... for input=...

Failures: 1, Success: 601


Better: use a testing framework like unittest, py.test or doctest. Here's how it looks like doctest:

def insertion_sort(numbs):
    """
    &gt;&gt;&gt; insertion_sort([5, 2, 4, 6, 1, 3])
    [1, 2, 3, 4, 5, 6]
    &gt;&gt;&gt; insertion_sort([190, 2, 4, 6, 1, 3])
    [1, 2, 3, 4, 6, 190]
    &gt;&gt;&gt; insertion_sort([-190, 2, 4, 6, 1, 3])
    [-190, 1, 2, 3, 4, 6]
    &gt;&gt;&gt; insertion_sort([0, 0, 4, 6, 1, 3])
    [0, 0, 1, 3, 4, 6]
    &gt;&gt;&gt; insertion_sort([0, 0])
    [0, 0]
    &gt;&gt;&gt; insertion_sort([1, 0])
    [0, 1]
    """
    for cur_index in range(1, len(numbs)):
        cur_val = numbs[cur_index]

        lookup_index = cur_index - 1

        while lookup_index &gt;= 0 and numbs[lookup_index] &gt;= cur_val:
            numbs[lookup_index + 1] = numbs[lookup_index]
            lookup_index -= 1
        numbs[lookup_index + 1] = cur_val

    return numbs


if __name__ == "__main__":
    import doctest
    doctest.testmod()


Now, this is a bit verbose, so my preference would probably to use py.test or nosetests, but the advantage of doctest is that it's built-in, and for simple tests it is sufficient.

Now, for another point. Your sorting is both in-place, and returns the list again.

For instance:

&gt;&gt;&gt; example = [6, 7, 1, 2]
&gt;&gt;&gt; result = insertion_sort(example)
&gt;&gt;&gt; result
[1, 2, 6, 7]
&gt;&gt;&gt; example
[1, 2, 6, 7]


Suggestion: if you do in-place sort, make sure it is documented that the function modifies the input array. In general, I would prefer not returning the result, as to limit confusion.

I can't really see anything wrong with the implementation itself, but I'm not familiar enough with insertion sort.

Using range is fine for short lengths, but as this is a list-sort, you don't really know the values len(numbs) will take. Maybe use xrange instead? (Or better: upgrade to Python 3!).

One thing I would really really like to advice is switching to Python 3 for learning. Python 2 is only supported up-to 2020, and a lot of people would be happy if more people learn Python 3 instead.