pythonpython-3.xrecursion

Why do I get a recursion error when the depth of the expected recursion should be way less than 999?


What makes this code recurse so many times? I was expecting to have a number of iterations much lower than 999.

I tried hard-coding the list of values (which is what you see in the version I gave) and reducing the number of elements in the list: With a single element, the code runs great, but anything above that raises the error seen below.

def main():
    list_to_sort: list[int] = [50, 3, 500, 90, 1, 1]

    def sort(param_to_sort: list[int]) -> list[int]:
        param_len = len(param_to_sort)
        if param_len == 1:
            return param_to_sort
        else:
            half_len: int = param_len // 2
            list1: list[int] = sort(param_to_sort[0:half_len])
            list2: list[int] = sort(param_to_sort[half_len:-1])
            if list1[-1] <= list2[0]:
                return [*list1, *list2]
            else:
                return [*list2, *list1]

    sorted_list: list[int] = sort(list_to_sort)

    print(f"The sorted list is as follows:\n{sorted_list}")


if __name__ == '__main__':
    main()

The error returned is as follows:

Traceback (most recent call last):
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 31, in <module>
    main()
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 22, in main
    sorted_list: list[int] = sort(list_to_sort)
                             ^^^^^^^^^^^^^^^^^^
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 15, in sort
    list2: list[int] = sort(param_to_sort[half_len:-1])
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 15, in sort
    list2: list[int] = sort(param_to_sort[half_len:-1])
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 14, in sort
    list1: list[int] = sort(param_to_sort[0:half_len])
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 14, in sort
    list1: list[int] = sort(param_to_sort[0:half_len])
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 14, in sort
    list1: list[int] = sort(param_to_sort[0:half_len])
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 992 more times]
  File "/home/aengusb/Desktop/PyCharmProjects/recursiveSort/recursiveSort.py", line 13, in sort
    half_len: int = (param_len / 2).__floor__()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object

The line numbers might not match exactly because I have a few commented-out lines from trying to figure this issue out myself.


Solution

  • The problem is that you bump into an infinite recursion.

    This is caused by param_to_sort[half_len:-1], which excludes the value at -1, i.e. the last value. This will cause endless recursion when the size of param_to_sort is 2, making this slice empty, and you have no base case treatment for empty lists.

    To slice from half_len onwards, do param_to_sort[half_len:]: now you won't skip the last value in the list.

    Related to this: you should foresee the case where the list is empty. You can use the base case you already have implemented and also execute it when the list size is 0 instead of 1.

    Another issue is that in the merge step you just foresee two possible arrangements: either the whole of list1 to come before the whole of list2, or vice versa. But there are more possibilities. For instance, if list1 is [2, 4] and list2 is [1, 3] you don't want [*list1, *list2] nor [*list2, *list1]. You'll want to "weave" the values from these lists, which often requires that you make more than one comparison and step by step pick a value from either list. There are several ways you can implement that merge step.

    Here is how you could make it work:

        def sort(param_to_sort: list[int]) -> list[int]:
            param_len = len(param_to_sort)
            if param_len <= 1:  # Also when list is empty
                return param_to_sort
            else:
                half_len: int = param_len // 2
                list1: list[int] = sort(param_to_sort[:half_len])
                list2: list[int] = sort(param_to_sort[half_len:]) # Fix
                # Merge step needs to check how to "weave" the two lists
                merged: list[int] = []
                while list1 and list2:
                    source: list[int] = list1 if list1[-1] > list2[-1] else list2
                    merged.append(source.pop())  # add greatest
                return list1 + list2 + merged[::-1]