A recursive function is a nice way to do this:

def collect_folders(start, depth=-1)
    """ negative depths means unlimited recursion """
    folder_ids = []

    # recursive function that collects all the ids in `acc`
    def recurse(current, depth):
        folder_ids.append(current.id)
        if depth != 0:
            for folder in getChildFolders(current.id):
                # recursive call for each subfolder
                recurse(folder, depth-1)

    recurse(start, depth) # starts the recursion
    return folder_ids
Answer from Jochen Ritzel on Stack Overflow
๐ŸŒ
Plain English
python.plainenglish.io โ€บ replacing-for-loops-with-recursion-in-python-49e74290f8f
Replacing For Loops With Recursion in Python | by Julio Souto | Python in Plain English
May 25, 2021 - # Using for loopdef for_loop1(): for i in range(10): print(f'I have {i} cake(s).') for_loop1() # Using Recursiondef recursive1(i=0): ### base case ### if i == 9: print(f'I have {i} cake(s).') return True # return True to stop the loop ### base case ### print(f'I have {i} cake(s).') i+=1 # update the value of i return recursive1(i) # return itself with the updated i recursive1()
Discussions

Converting a for loop to recursion in Python - Stack Overflow
For completeness, here is one that does. Recursive functions usually have two parts. Base case: Handled directly. Corresponds to the body of the innermost loop in your code. More on stackoverflow.com
๐ŸŒ stackoverflow.com
October 29, 2016
python - I'm practicing turning for loops into recursive functions. what do you think? - Code Review Stack Exchange
I was thinking I could get a better grip on recursion by doing some programs that were easier to understand. While that helped a little, I see I need to step it up. \$\endgroup\$ ... Find the answer to your question by asking. Ask question ... See similar questions with these tags. ... 2 Universal class for ... More on codereview.stackexchange.com
๐ŸŒ codereview.stackexchange.com
April 15, 2017
python - How to use recursion for "for" loops - Stack Overflow
My code is meant to find the longest path in a matrix, where each value is greater than the one previous. However, I've been instructed to not use for loops at all, which is difficult because I hav... More on stackoverflow.com
๐ŸŒ stackoverflow.com
Loops vs recursion
Every recursive solution can be expressed as a loop, and vice versa. In Python, you use recursion when the recursive solution makes it easier to understand (you would probably use a loop for a factorial) Recursions are heavily used in functional programming (which Python does not excel at), and Guido said he prefers loops instead of recursion. If you like recursion and mathematical elegance in programming, I recommend you take a look at functional programming and functional programming languages. Be advised though that functional programming can be difficult to grasp at first. More on reddit.com
๐ŸŒ r/learnpython
35
109
April 30, 2018
Top answer
1 of 2
1

but I wonder if it's okay there's no "return" line under "else:"

Yes, that's OK. You don't need to return anything from your function if you don't want to. In fact, in the interest of consistency, you may as well remove the thing returned in the if block too:

def printElement(inputlist):
        newlist=inputlist
        if len(newlist)==0:
                return
        else:
                removedElement=newlist[len(inputlist)-1]
                newlist=newlist[:len(inputlist)-1]
                Element=printElement(newlist)
                print(removedElement)


collection = ['hey', 5, 'd']

printElement(collection)

Is it better code with or without the newlist?

Assigning new things to inputlist won't modify it outside of the function, so there's no harm in doing so. May as well get rid of newlist.

def printElement(inputlist):
        if len(inputlist)==0:
                return
        else:
                removedElement=inputlist[len(inputlist)-1]
                inputlist=inputlist[:len(inputlist)-1]
                Element=printElement(inputlist)
                print(removedElement)


collection = ['hey', 5, 'd']

printElement(collection)

you don't use Element after assigning it, so you may as well not assign it at all.

def printElement(inputlist):
        if len(inputlist)==0:
                return
        else:
                removedElement=inputlist[len(inputlist)-1]
                inputlist=inputlist[:len(inputlist)-1]
                printElement(inputlist)
                print(removedElement)


collection = ['hey', 5, 'd']

printElement(collection)

You don't really need to modify inputlist, since you only use it once after modifying it. Just stick that expression straight into the printElement call. And now that inputlist is never modified, you can get rid of removedElement too, and just inline its expression in the print function.

def printElement(inputlist):
        if len(inputlist)==0:
                return
        else:
                printElement(inputlist[:len(inputlist)-1])
                print(inputlist[len(inputlist)-1])


collection = ['hey', 5, 'd']

printElement(collection)

Fun fact: for any list x, x[len(x)-1] can be shortened to x[-1]. Same with x[:len(x)-1] to x[:-1].

def printElement(inputlist):
        if len(inputlist)==0:
                return
        else:
                printElement(inputlist[:-1])
                print(inputlist[-1])


collection = ['hey', 5, 'd']

printElement(collection)

Since the first block unconditionally returns, you could remove the else and just put that code at the function level, without changing the code's behavior. Some people find this less easy to read. Personally, I like my code to have the least amount of indentation possible.

def printElement(inputlist):
        if len(inputlist)==0:
                return
        printElement(inputlist[:-1])
        print(inputlist[-1])


collection = ['hey', 5, 'd']

printElement(collection)

That's about as compact as you can get, with a recursive solution. You should probably just stick with the iterative version, for a few reasons:

  • Fewer lines
  • More easily understood
  • Doesn't raise a "maximum recursion depth exceeded" exception on lists with 200+ elements
2 of 2
1

Your function is needlessly complicated. The return value is never used. You also print from the end of the list, which is a bit odd: tail recursion is a more usual style. For example:

def printCollection(c):
    if c:
        print c[0]
        printCollection(c[1:])

This still has the flaw that it copies the list for every element in the list, making it an O(n^2) function. It's also limited to data structures that use slices and indices. Here's a recursive version that prints any iterable:

def printCollection(c):
    it = iter(c)
    try:
        el = it.next()
        print el
        printCollection(it)
    except StopIteration:
        pass

It's still a bit odd to recurse here as this is a naturally iterative problem.

๐ŸŒ
HackerNoon
hackernoon.com โ€บ recursion-vs-looping-in-python-9261442f70a5
Recursion vs. Looping in Python | HackerNoon
January 17, 2019 - ... Recursion occurs when any function calls itself. One of the big differences between recursion and looping is the way that a recursive function terminates. In the above example, a for loop ends at the end of the sequence it is looping over.
๐ŸŒ
W3Schools
w3schools.com โ€บ python โ€บ python_recursion.asp
Python Recursion
Python Examples Python Compiler Python Exercises Python Quiz Python Challenges Python Practice Problems Python Server Python Syllabus Python Study Plan Python Interview Q&A Python Training ... Recursion is when a function calls itself. Recursion is a common mathematical and programming concept. It means that a function calls itself. This has the benefit of meaning that you can loop through data to reach a result.
Find elsewhere
Top answer
1 of 2
1

Recursion is about a base case and an iterative case. In your situation, think of the smallest matrix you can - an empty matrix. That is your base case. Your function should return a path length of 0 if it is empty.

The iterative case is a bit more difficult, and usually where things become confusing. The key goal of the iterative case is to reduce the size of the problem, usually by the smallest amount possible.

To overly simplify, if you start with a function like this:

def f(ls):
    for x in ls:
        result = g(x, result)
    return result

Then the iterative version looks like this:

def f(ls, result):
    if 0 == len(x):  # Base Case
        return result
    else:  # Iterative Case
        result = g(x, result)
        return f(ls[1:], result)

The trick is figuring out what of your internal logic goes into g() and how to represent the result.

Let's take a simpler version of your problem, where we deal with a single array and want to return the longest 'path' in that array. A path is a sequence of integers that are incrementing by one.

So, if we have [0,1,2,3] the expected value is 4. If we have [0,1,1,2,3] the expected value is 3. Similarly, 3 would be expected for the input [0,1,2,2,1]. [3,2,1] should return 0.

The most basic signature we require is this:

def f(ls: list[int]) -> int

Essentially, 'a function that takes a list of ints and returns the length of the longest path'. But we have to remember a bit of extra state to do this properly. Specifically, we need to remember the length of the current path we are on, and the lengths of all paths we have found.

def f(ls: list[int], current_path: int) -> int

Let's examine the base case. A base case is any case where reducing the size of your input (in our case 'input' really refers only to ls) would not yield an easier problem to solve. There are two base cases - if the input list is length 0 or 1. In both these cases, we have no need to shrink the problem any further.

def f(ls: list[int], current_path: int) -> int:
    # If there are no elements, current_path is the only valid length
    if 0 == len(ls):
         return current_path
    # If there is one element, increment current_path before returning it, to account for the length that element adds
    if 1 == len(ls):
         return current_path + 1
        

These serve to terminate the recursion. Now lets look at the iterative case:

def f(ls: list[int], current_path: int) -> int:
    # Base cases
    if 0 == len(ls):
        return current_path
    if 1 == len(ls):
        return current_path + 1
    # Iterative case - guaranteed that len(ls) >= 2
    current_path = current_path + 1  # Increment the current_path to account for the current element.
    if ls[1] == ls[0] + 1:
        # In this branch we know that the path will continue.
        return f(ls[1:], current_path)  # ls[1:] reduces our problem space by one element
    else:
        # In this branch the path ends, because the next element breaks the incremental sequence.
        recursive_result = returnf(ls[1:], 0)  # Reduce the problem space and start a new path of length 0
        return max(recursive_result, current_path)  # Choose which path is longer

There is a lot going on here, so I'll break it down in parts. The key thing to remember is that we are going to reduce the problem space - shorten the list - and then recurse with that smaller problem space. The element we are removing is therefore key to determining how we proceed.

Since we are removing one element, we add one to the current path. If the incoming path length was 0, we now have a path length of 1. If it was 3, we now have a path of 4.

Then we check the value of the next element. Is it exactly larger than the current element? If so, we know the path will continue, so we recurse, passing along a list without the current element and the length of our current path.

If it is not exactly one more, we know our current path ends here. In the recursion we pass along a new path length of 0, resetting the counter. But we have to do something with the returned value - decide whether it is larger than the current path as it stands at this element. Hence using max() to choose between the two possibilities.

This gives you a recursive function that iteratively shortens the problem at each step until it finds a base case, at which point it returns - but it returns up through the recursive function, accumulating the results.

(n.b. There are ways to optimize this, clean it up, add default values, etc. I'm skipping that because it doesn't help you think about the recursion.)

Your actual problem is harder. You're going along a two dimensional array. The key insight to have is this: in the iterative step in the example I gave, I looked at all the possible cases for moving forward and chose between them. However, you can go down all possible paths. If you are at a particular element in a two dimensional array, you know that you can go one way or the other - that's two recursive function calls. Because recursion is shortening your problem space, your iterative step can simply trust it will return, and only deal with the results. In your case, that is choosing which of the two recursive calls you made returned the larger result.

(At this point I have to make assumptions about your problem because you included neither a complete specification nor full code.)

def f(matrix: list[list[int], coords: (int, int), current_path: int) -> int:
    # Find all possible 'next steps'. For a next step to be valid it must be exactly one greater than the current location.
    # Base Case - There are no possible next steps -> return current path + 1
    # Increment path
    # Iterative cases
        # There is only one next step -> recurse passing new coordinates and path length
        # There are two or three next steps -> recurse passing new coordinates and path length, then choose which result is the longest.

The difficulty here is that this finds the longest path from any given starting position. To truly find the longest path in the matrix, you would have to add a fourth argument to your function - a list of all the starting positions you have tried. Then you would change your logic for finding the next steps from 'is it strictly one larger' to 'is it strictly one larger or have I not tried starting from that point'?

# Use type aliases so you're clear about the types
type Matrix: list[list[int]]
type Coordinate: (int, int)  # x-y coordinates
type Cache: list[Coordinate]  # All the places we've started from

def f(matrix: Matrix, 
      coords: Coordinate, 
      current_path: int, 
      starting_points: Cache) -> int:
    if 0 == len(matrix):
      return current_path
    if 1 == len(matrix) and 0 == len(matrix[0]):
      return current_path
    current_path = current_path + 1  # From here on, we have a valid element at this coordinate
    if 1 == len(matrix) and 1 == len(matrix[0]):
      return current_Path
    moves = get_all_moves(...)
    if 0 == len(moves):  # This is *also* a base case - the problem cannot be shrunk any further!
      return current_path
    results = try_each_move(moves)  # This is also a recursive function... but a *different* recursive function, in order to avoid using a for loop (or list comprehension)
    return max(max(results), current_path)

A few closing notes:

  • Do try to adhere to python style guides. It makes reading python easier!
  • Any information you think you need to store outside the function can just be passed as a parameter. Pure recursive functions don't have access to a closure or outer scope. Depending on your case, this may not be the most efficient solution, but it is where you should start until you're much more comfortable with recursion.
  • Similarly, if copying a value rather than a reference makes it easier for your to reason about what you're doing, do that first. Efficiency is for later. Clarity first.
  • Recursion often (though not always) easier if you're building up a solution from the return of the recursion call. In the examples here, the max() function is doing that accretion, but you could imagine inverting the approach here and first doing the recursive call, which returns two values - the value of the last element and the length of the path. Then you could decide if you're smaller than that value. I didn't do that here because you'd have to remember two path lengths at a time.
  • In this specific problem, do take care with the cache. You can't just remember if you've ever visited a coordinate.
2 of 2
-1

Recursion is just a function that keeps calling itself until it reaches a condition that disallows it from continuing.

Here's an example that's themed toward your needs.

""" Emulation Of:
for row in range (len(matrix)):
    for col in range (len(matrix[0])):
        print(matrix[row][col])
"""

matrix = [[1,2,3],[4,5,6],[7,8,9]]

#wrapping everything in this function makes `i` unreachable
#because it should be managed internally
def process_matrix(r:int, c:int) -> None:
    
    def columns(i:int=0, r:int=0) -> None:
        if i==c: return     #columns finished
        print(matrix[r][i]) #work
        columns(i+1, r)     #next column

    def rows(i:int=0) -> None:
        if i==r: return     #rows finished
        columns(0, i)       #recurse all columns for this row
        rows(i+1)           #next row
        
    rows(0)                 #start recursion

#use    
process_matrix(len(matrix), len(matrix[0]))

If you are trying to retrieve data, you have to return the "recursion call". Otherwise, you'll get None back from the very first call, and the recursion will carry on in a way that is unreachable by your code.

data = [10,20,30,40,50,60]   

def where_is_50(i:int=0) -> int:
    if data[i] == 50:
        return i            #stop recursion
    return where_is_50(i+1) #next
 
print(where_is_50())

If it isn't clear, The first time the function is called, it is not 50 so, it returns a call to itself. However, the actual return can't finish until the call does. Essentially, you end up with a string of "active" functions that are all waiting for the call that finds 50. When 50 is found, the return value keeps ascending through all the calls back to the very first one.

Whatever recursive functions you make should have a local reference to the data to traverse. In other words, don't pass your entire matrix on each call. Pass names or indexes recursively.

๐ŸŒ
Reddit
reddit.com โ€บ r/learnpython โ€บ loops vs recursion
r/learnpython on Reddit: Loops vs recursion
April 30, 2018 -

So I was watching this video on youtube and was simply amazed by this function to calculate factorials with recursions.

def factorials(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

Since I'm pretty new to python and coding in general (I'm a lawyer by profession), I would have solved this problem with loops. The moment I understood the concept of this function, I was baffled by the simplicity and beauty of it.

Now here's my question:

  1. In which cases is it smarter to use one or the other and what are the benefits? (if there even is a general rule)

  2. Where can I learn more about recursions? Are there any tutorials online?

Thanks :)

Top answer
1 of 5
36
Every recursive solution can be expressed as a loop, and vice versa. In Python, you use recursion when the recursive solution makes it easier to understand (you would probably use a loop for a factorial) Recursions are heavily used in functional programming (which Python does not excel at), and Guido said he prefers loops instead of recursion. If you like recursion and mathematical elegance in programming, I recommend you take a look at functional programming and functional programming languages. Be advised though that functional programming can be difficult to grasp at first.
2 of 5
20
You CAN solve almost any problem using almost any loop. But when is it more appropriate to use which loop, my opinion is this: A while loop is the correct solution when the data amount and endpoint is unknown. A for loop is the correct solution when the amount of data, and endpoint is known. A Recursion is the correct solution when the amount is known, but the end point is not. So what I mean by that is, imagine finding a file in a bunch of folders. Inside every folder the AMOUNT of files are known, so you use a for loop to walk through the files and you pick out the right file. But how many folders do you need to go through? You don't know that, because every subfolder COULD have another subfolder, or not. And so the folder structure branches out, in an unknown way. This becomes difficult to deal with using multiple for loops. Again, because the endpoint is unknown. Here recursion is the perfect solution. find_file(folder): global filelist for file in folder.files: if ".jpg" in file.name: filelist.append(file) for subfolder in folder.dirs: find_file(subfolder)
๐ŸŒ
Reddit
reddit.com โ€บ r/learnprogramming โ€บ [python] converting nested for-loop into recursion
r/learnprogramming on Reddit: [Python] Converting Nested For-Loop into Recursion
February 17, 2020 -

Hey guys, I'm pretty new to python and I'd like to practice using Recursion in my code to further assist my understandings with the recursive function. From what I am aware of, identifying a base case is required to prevent infinite recursion but I cannot seem to figure out the base case for this scenario.

Simplified code as shown below:

for y in range(N):         
    for x in range(M):             
        if origin[x][y] == 1 and CellsMap[x][y] == 0:                        
           CellsCounter += 1             
        DiscoverCells(origin, x, y, CellsCounter)  

Basically the above code will scan for every (x, y) coordinate in a grid (Implemented by the nested For Loop)

How do I go about converting this code into recursion, with a call to function of def DiscoverCells()?

Thanks in advance!!

Top answer
1 of 3
3
First, let's eliminate for loops because we want to avoid thinking about what a for loop in Python translates to. y = 0 while y < N: x = 0 while x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) x = x + 1 y = y + 1 What state do we have N, y, M, x, origin and CellsCounter. so let's start with making a function taking these inputs as arguments. def f(y, N, x, M, origin, CellsCounter): pass This looks too complicated to do all at once so lets start with the inner loop while x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) x = x + 1 will be converted to def inner(y, N, x, M, origin, CellsCounter): pass First, let's copy paste the body of the loop into inner def inner(y, N, x, M, origin, CellsCounter): if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) x = x + 1 Now, at the end we need to do the following logic if while condition is true: loop again else: return result So, let's just put that logic in def inner(y, N, x, M, origin, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(y, N, x + 1, M, origin, CellsCounter) else: return (y, N, x, M, origin, CellsCounter) Notice that we return all things that can change during any execution of our loop. We'll eliminate the unchanging stuff later. Now, we do a similar thing for the outer loop y = 0 while y < N: x = 0 INNER LOOP y = y + 1 Converting to a function outer def outer(y, N, x, M, origin, CellsCounter): x = 0 INNER LOOP y = y + 1 Adding the looping logic back in def outer(y, N, x, M, origin, CellsCounter): if y < N: x = 0 INNER LOOP return outer(y + 1, N, x, M, origin, CellsCounter) else: return (y, N, x, M, origin, CellsCounter) How does inner loop work? We call the inner loop function and then update all 6 of our variables to what they are after performing the inner loop. def outer(y, N, x, M, origin, CellsCounter): if y < N: x = 0 (y, N, x, M, origin, CellsCounter) = inner(y, N, x, M, origin, CellsCounter) return outer(y + 1, N, x, M, origin, CellsCounter) else: return (y, N, x, M, origin, CellsCounter) Now let's pretty some stuff up. First, notice that the value x is never changed between calls to outer so let's eliminate that variable from outer It also won't get us def outer(y, N, M, origin, CellsCounter): if y < N: (y, N, x, M, origin, CellsCounter) = inner(y, N, x, M, origin, CellsCounter) return outer(y + 1, N, M, origin, CellsCounter) else: return (origin, CellsCounter) Also, the caller of outer won't use y or N or x or M as passed into outer so let's avoid returning those. Let's look at inner some more. def inner(y, N, x, M, origin, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(y, N, x + 1, M, origin, CellsCounter) else: return (y, N, x, M, origin, CellsCounter) our caller doesn't need us to tell it what y, x, N and M are after we finish our loop iteration so let's not bother returning those values. def inner(y, N, x, M, origin, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(y, N, x + 1, M, origin, CellsCounter) else: return (origin, CellsCounter) Notice that we no longer use N anywhere so we can eliminate that too. def inner(y, x, M, origin, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(y, x + 1, M, origin, CellsCounter) else: return (origin, CellsCounter) So our two functions that do recursion are def outer(y, N, M, origin, CellsCounter): if y < N: (origin, CellsCounter) = inner(y, x, M, origin, CellsCounter) return outer(y + 1, N, M, origin, CellsCounter) else: return (origin, CellsCounter) def inner(y, x, M, origin, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(y, x + 1, M, origin, CellsCounter) else: return (origin, CellsCounter) Now the only variables that "change" between loops are x, y, and CellsCounter so we can use nested functions to avoid passing those as inputs (and because origin doesn't change we can avoid returning that too. def big(N, M, origin): def outer(y, CellsCounter): if y < N: CellsCounter = inner(y, x, CellsCounter) return outer(y + 1, CellsCounter) else: return CellsCounter def inner(y, x, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(y, x + 1, CellsCounter) else: return CellsCounter return outer(y=0, CellsCounter=0) And likewise if we do another nested definition, inner never changes y so it can just be a closure capturing y from outer. def big(N, M, origin): def outer(y, CellsCounter): def inner(x, CellsCounter): if x < M: if origin[x][y] == 1 and CellsMap[x][y] == 0: CellsCounter += 1 DiscoverCells(origin, x, y, CellsCounter) return inner(x + 1, CellsCounter) else: return CellsCounter if y < N: CellsCounter = inner(x=0, CellsCounter) return outer(y + 1, CellsCounter) else: return CellsCounter return outer(y=0, CellsCounter=0)
2 of 3
1
To all following commenters: please, do not bring up the old circlejerk jokes/memes about recursion ("Understanding recursion...", "This is recursion...", etc.). We've all heard them n+2 too many times. I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
๐ŸŒ
AskPython
askpython.com โ€บ home โ€บ python recursion example โ€“ recursive functions
Python Recursion Example - Recursive Functions - AskPython
September 5, 2019 - Python supports recursive functions. The recursion is very similar to a loop where the function is called in every iteration. Thatโ€™s why we can always use loops as a replacement for Python recursion function.
Top answer
1 of 2
5

The standard structural recursive formula (and the one you'd use if you were using a functional language like Scheme) would be to deconstruct the list recursively:

func([]) => nothing
func([x, ...]) => do_stuff(x), func([...])

Therefore, the "functional" way to do this would be to take a single list (not an index), and to recurse on smaller lists:

def rec_list(l):
    if not l: return # empty list case
    # process l[0]
    return rec_list(l[1:])

Note that this is terribly, terribly inefficient because of the l[1:], but it's the basis for understanding more complex recursive constructs (e.g. recursing on binary trees).

We can do interesting things with this kind of structural recursion. For example, here's how you'd reverse a list in a functional language:

def rev_list(l):
    if not l: return []
    return rev_list(l[1:]) + [l[0]]

(Of course, you could just do l[::-1] in Python, but here we're trying to show how it would be done recursively).

2 of 2
1

So you want to do away with a nice (mostly) well coded loop? (mostly because you probably want to use enumerate instead of range(len(lst))) -- enumerate is pretty cool, once you start using it, you'll never look back.

Anyway, I guess we can do that:

def silly_loop(lst,index=0):
    try:
       #do something with index and lst here
       silly_loop(lst,index=index+1)
    except IndexError:  #or maybe a different error, depending on what you're doing with index ...
       return

an example:

def silly_loop(lst,index=0):
    try:
       print lst[index]
       silly_loop(lst,index=index+1)
    except IndexError:
       return

a = range(10)
silly_loop(a)

Note that I can't think of ANY reason why you would want to do this in real code, (But, if you're just doing this to teach yourself about recursion, then I hope this helps).

Top answer
1 of 3
1

So in a recursive solution you dont use a for loop but instead utilise the functional programing model of using the stack as your loop

def recur_factorial(n):
    total = 1
    if n > 1:
        total = n * recur_factorial(n - 1)
    print(total)
    return total


recur_factorial(5)

OUTPUT

1
2
6
24
120

This hopefully gives you a better idea and if your requirement changes you can just adapt this model to achieve what you need.

UPDATE

Just realised in your comment you said the function needs to return a list of factorials from 2 to n. so below is updated code to return a list.

def recur_factorial(n, factorials=[]):
    if n == 1:
        factorials.append(1)
    else:
        total = n * recur_factorial(n - 1)[-1]
        factorials.append(total)
    return factorials

factorials = recur_factorial(10)
print(factorials)

OUTPUT

[2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
2 of 3
1

As already pointed out the concrete problem is that you return within the for loop which ends the function completely.

However, assuming that you would like to recursively implement the calculation of a factorial I'd suggest to revisit your code completely. the real 'issue' is not the for loop and the return statement but the way you approach this.

Basically what you are doing is mixing a recursive and an iterative implemention. The iterative implementation uses a for loop and then does not need to call itself (because it 'knows' how many times to run the loop (i.e. n times) and do the single steps in the calculations).

the recursive implementation does not use a for loop and it does not 'know' how many times to run the individual calculation steps (not even at run time when the function is called the first time and the n is set). Therefore, think of a way to implement it recursively with no for loop at all! Recursively is more thinking but only 4 lines of code. But it will be more helpful if you try to come up with the solution yourself.

๐ŸŒ
Educative
educative.io โ€บ answers โ€บ what-is-the-difference-between-loops-and-recursion-in-python
What is the difference between loops and recursion in Python?
A loop is an instruction that tells the computer to repeatedly execute an instruction, usually based on a condition. We have three main kinds of loops: ... The latter is not available in Python and is only a little different from regular while loops. For loops are typically used to iterate over a finite number of items and perform an action on an item at a particular period in the loop, e.g., iterating over a list of names and printing them.
๐ŸŒ
Medium
jasondeden.medium.com โ€บ recursive-functions-in-python-a-visual-walk-through-28cf22cc10e2
Recursive Functions in Python โ€” A Visual Walk-Through | by Jason Eden | Medium
November 3, 2021 - Recursive functions in Python, because it is an interpreted language and will run one line of code at a time by default, will finish one โ€œsideโ€ of the function completely to the bottom first, and then loop back up, iterating at each point up and down the logical tree until youโ€™ve finished all processing (reached a stop criteria) for all nodes and returned to the root node.
๐ŸŒ
W3Schools
w3schools.com โ€บ python โ€บ gloss_python_function_recursion.asp
Python Function Recursion
Python also accepts function recursion, which means a defined function can call itself. Recursion is a common mathematical and programming concept. It means that a function calls itself. This has the benefit of meaning that you can loop through data to reach a result.
๐ŸŒ
Quora
quora.com โ€บ Can-we-use-while-loops-to-implement-recursion-in-Python
Can we use while loops to implement recursion in Python? - Quora
Answer (1 of 2): Yes but this is true for any recursion. even so besides while loops you are also going to need some kind of data structure to act as the stack, if your original recursion has more than 1 branch
Top answer
1 of 2
1

I think the key here is really in just getting used to thinking about recursion and how to solve problems with it. It really is a "work backwards" kind of a solution. With that in mind, let's see if we can develop a recursive solution together.

  1. Whenever you're writing something recursive, you're essentially always going to want to start at the end of the problem -- what will or should the behavior of the code be when it is finished, when it has found the answer? What overall environment and inputs should we expect at that final state? And, most importantly, how can we "propagate" our answer back up the call stack without losing anything we care about in the process?
    • First things first: before we can properly get started thinking about or writing any code, we need to make sure we're equipped with the right tools for the job: choosing and preparing the proper data structures (where minor differences in choice can be the difference between hacking away at an incorrect or suboptimal solution for days, or figuring out an elegant and optimal one in a few hours!) The dictionary you've provided as shown here really isn't doing you any favors: the values, as lists of lists, are difficult to check through cleanly and efficiently, Pythonically without a lot of cruft. Personally, I think this particular problem lends itself well to a dict of dicts, i.e.

      d = {
           'dict1': {
               'Movie1': 'Fiction',
               'Movie2': 'Romance'
            },
            etc...
          }
      

      considering that the dict values already form a natural association (in fact, they closely resemble a popular way of instantiating dicts: with a list of (key, value) tuples). If this doesn't seem like your cup of tea, I think another strategic choice would be having parallel dicts of lists -- sharing the same keys, but having one just for movie titles and one just for genres. The lists should be easy to use while coding your solution, but (unlike the nested dictionaries before -- dict_values and dict_keys are implemented as sets behind the scenes) won't make lookup quite as quick...so depending on your use case this would be less preferable. A quick illustrative example:

      movies = {'dict1': ['Movie1', 'Movie2'], 'dict2': ['Movie3']}
      genres = {'dict1': ['Fiction', 'Romance']}
      

      In this set-up, you'd first check through the level of dictionary keys and then the list indices of the value for the matching entry in the corresponding dictionary.

      In general, though, it can really simplify your code and your thought process to not try to cram too many different things (data with different meanings or uses) into the same object rather than having a neat, well-laid plan for utilizing a collection of objects designed to work together, especially if that can be done in a manner so as to not waste a lot of memory.

  • Now that we have a general plan (the dict-of-dicts approach first described above) we can return to our original question: where do we end when solving this problem? What is our base case (important terminology as you progress with recursion)? Well, that actually seems trivially easy -- the base case is when we are looking at some collection (a list, set, dict keys or values, what have you) and we find that it contains the movie title we are searching for. So we can already write our base case (partially)

    def recursive_movie_search(movie, db, ...):
        if movie in db:
            return movie, db[movie]
        # Rest of search logic goes here
    

    Notice that because of our choice of data structure, once we've found the movie title we've already got the genre as well, since they're formally associated. We're not quite done yet though, as hinted at by the comment and the ellipsis in the function signature above.

  • Here's the last challenge to the recursive technique -- how do we make it so that, by the time we find what we're looking for, we have any and all other information we need to produce our final answer? Often, as in this case, this has to do with cleverly passing along any necessary auxiliary information on our way (down the recursive call stack) to solving the problem. One simple way (not that there's only one!) to finish off the implementation in this case would be something like the following:

    def recursive_movie_search(movie, db, prev=None):
        prev = prev or []
    
        if movie in db:
            return prev.extend((movie, db[movie]]))
        for key in db:
            search = recursive_movie_search(movie, db[key], prev + [key])
            if search: return search
    
        return False
    

Now, to be fair, the problem as you gave it wasn't particularly well-suited to a recursive solution since the searching we needed to do was quite shallow. But the approach above should work with arbitrarily deeply nested dictionaries (then once you get the answer you can decide which of the keys along the path that led to it are significant and worth reporting, or should just be omitted). The only assumption made is that at the bottom level the movie titles will be associated with their genres.

Note also that there are several minor changes you could have made just based on personal choice: instead of accumulating the keys and passing them into the function, this could have been handled simply in the recursive call, e.g. search = key + recursive_movie_search(movie, db[key]) but then this would have required a slightly more thorough check on the next line to ensure that we got a "hit" with that call. Also, you could just as easily return None, an empty list [], or any other item whose boolean value evaluates to False (in the failure case where the movie isn't found) and this logic will work just as well. You just need some kind of flag there to tell you that the search failed -- you could even use a special string that you check for specifically.

P.S. This is my first answer on StackOverflow -- I really hope it helps! And I'm very much open to further questions or feedback on how I can improve :)

2 of 2
0

Assuming that your given structure does not change for other cases, I think this is what you are looking for:

dic = {
    "dict1": [["Movie1", "Fiction"], ["Movie2", "Romance"]],
    "dict2": [["Movie3", "Comedy"]],
}

def rec_search(data, movie_name):
    if isinstance(data, dict):
        for key in data.keys():
            result = rec_search(data[key], movie_name)
            if isinstance(result, tuple):
                return f"found in {key}; Title: {result[0]}; Genre: {result[1]}"
        return None
    elif isinstance(data, list):
        for element in data:
            if isinstance(element, str):
                if element == movie_name:
                    return element
            else:
                result = rec_search(element, movie_name)
                if result:
                    return (result, element[1])
        return None


print(rec_search(dic, "Movie2"))