pythonmergesort

Merge sorting algorithm works for small list but crashes with bigger ones


I'm trying to recreate the merge sorting algorithm and while my code works for lists with length 4 or less when the length gets bigger it crushes.

As you'll see the error says that on some point sorted_right becomes NoneType. I tried debugging it but couldn't understand why this happens. Can anyone help me with this?

My code:

my_list = [5, 35, 10, 1, 0]

def merge_sort(array):
    if len(array) == 1:
        return array
    elif len(array) == 2:
        if array[0] > array[1]:
            array[0], array[1] = array[1], array[0]
        return array
    else:
        sorted_part = []
        left = array[:len(array)//2]
        right = array[len(array)//2:]
        sorted_left = merge_sort(left)
        sorted_right = merge_sort(right)
        for i in range(len(array)):
            if len(sorted_left) == 0:
                sorted_part.extend(sorted_right)
                break
            elif len(sorted_right) == 0:
                sorted_part.extend(sorted_left)
                break
            else:
                if sorted_left[0] < sorted_right[0]:
                    sorted_part.append(sorted_left.pop(0))
                else:
                    sorted_part.append(sorted_right.pop(0))
    print(sorted_part)


merge_sort(my_list)

The error:

[0, 1, 10]
Traceback (most recent call last):
  File "C:\Users\User\PycharmProjects\Mathima 15\askisis 10 merge sort.py", line 32, in <module>
    merge_sort(my_list)
  File "C:\Users\User\PycharmProjects\Mathima 15\askisis 10 merge sort.py", line 21, in merge_sort
    elif len(sorted_right) == 0:
         ^^^^^^^^^^^^^^^^^
TypeError: object of type 'NoneType' has no len()

Process finished with exit code 1

Solution

  • If you're going to use the return value from a recursive function in the function, it needs to return in every scenario. Your else branch has no such return. The solution is simple: return sorted_part.

    def merge_sort(array):
        if len(array) == 1:
            return array
        elif len(array) == 2:
            if array[0] > array[1]:
                array[0], array[1] = array[1], array[0]
            return array
        else:
            sorted_part = []
            left = array[:len(array)//2]
            right = array[len(array)//2:]
            sorted_left = merge_sort(left)
            sorted_right = merge_sort(right)
            for i in range(len(array)):
                if len(sorted_left) == 0:
                    sorted_part.extend(sorted_right)
                    break
                elif len(sorted_right) == 0:
                    sorted_part.extend(sorted_left)
                    break
                else:
                    if sorted_left[0] < sorted_right[0]:
                        sorted_part.append(sorted_left.pop(0))
                    else:
                        sorted_part.append(sorted_right.pop(0))
            return sorted_part
    

    The print at the end of the function is extraneous now as it would never be reached.

    This also seems like a good place to apply the match/case syntax introduced in Python 3.10.

    def merge_sort(array):
        match array:
            case [_]: return array
            case [a, b] if a < b: return array
            case [a, b]: return [b, a]
            case _:
                sorted_part = []
                left = array[:len(array)//2]
                right = array[len(array)//2:]
                sorted_left = merge_sort(left)
                sorted_right = merge_sort(right)
                for i in range(len(array)):
                    if len(sorted_left) == 0:
                        sorted_part.extend(sorted_right)
                        break
                    elif len(sorted_right) == 0:
                        sorted_part.extend(sorted_left)
                        break
                    elif sorted_left[0] < sorted_right[0]:
                        sorted_part.append(sorted_left.pop(0))
                    else:
                        sorted_part.append(sorted_right.pop(0))
                return sorted_part
    

    As a sidenote: array is a potentially misleading name for a list.

    You may also wish to return a new list in the simpler cases.

            case [_]: return list(array)
            case [a, b] if a < b: return list(array)