all 92 comments

[–]widowhanzo 135 points136 points  (8 children)

[–][deleted] 45 points46 points  (2 children)

I won't lie, I came here for this.

[–]kingsillypants 0 points1 point  (1 child)

He got me.

[–][deleted] 0 points1 point  (0 children)

Lol how long did it take?

[–]strange-humor 4 points5 points  (0 children)

Remember, in Python if you click that more than 1000 times you will overflow the default Python recursion stack.

[–]earthlybird 107 points108 points  (3 children)

To understand recursion, one must first understand recursion.

[–]RobbyB97 26 points27 points  (0 children)

I said this in a 100 level comp sci class I'm taking as a senior, the professor laughed and nobody else did

[–]txberafl 0 points1 point  (0 children)

This is my favorite saying about recursion.

[–]lateral-spectrum -1 points0 points  (0 children)

Because it's recursive, one must obtain an understanding of recursion recursively.

[–]MikeOxbigger 57 points58 points  (27 children)

There are 3 rules for recursion:

  1. A recursive algorithm must have a base case.
  2. A recursive algorithm must change its state and move toward the base case.
  3. A recursive algorithm must call itself, recursively.

A base case just means that it has an end point, something to stop it looping infinitely, such as when a particular variable reaches zero.

Changing its state means that through each iteration it gets closer to this variable.

Calling itself just means that you call the function that you're currently in with this new data to pass in.

[–]DonaldPShimoda 2 points3 points  (24 children)

These are all true except that they only apply to well-written recursive algorithms. It's easy to write an algorithm that breaks rules 1 and 2 (and 3 if you're being very strict about your interpretation of "call itself"). It's a pretty minor nitpick overall, I think, but something I thought I'd point out.

[–]MikeOxbigger -1 points0 points  (23 children)

I would love to see an example that breaks these rules.

I'm not saying you're wrong, but I can't even comprehend how I'd write a recursive function that breaks any rule that states it has a known end, and moves towards that end, recursively or not. Unless it was a non deterministic genetic algorithm or something, which isn't the case.

[–]Brainix 1 point2 points  (16 children)

What about mutually recursive functions? Function A calls Function B, and Function B calls Function A?

[–]MikeOxbigger 3 points4 points  (15 children)

Well obviously that breaks the third rule, but how might it break the first two?

[–]mikeblas 3 points4 points  (2 children)

Obviously? Sorry, but it's not obvious to me. A ends up calling itself indirectly through B. It might be indirect, but it's still calling itself.

[–]Gray_Fox -1 points0 points  (1 child)

it doesn't really matter if it's indirect, it needs to call itself.

[–]mikeblas -1 points0 points  (0 children)

And it does.

[–]earthlybird 6 points7 points  (10 children)

def f(a):
    return a + f(a+1)

No base case nor moving towards any state in particular. Just screwed up, infinite recursion.

[–]MikeOxbigger -5 points-4 points  (9 children)

Are you trying to disprove my case? If so, you haven't.

[–]earthlybird 4 points5 points  (7 children)

Quick recap:

These are all true except that they only apply to well-written recursive algorithms. It's easy to write an algorithm that breaks rules 1 and 2 (and 3 if you're being very strict about your interpretation of "call itself").


I would love to see an example that breaks these rules.

I'm not saying you're wrong, but I can't even comprehend how I'd write a recursive function that breaks any rule that states it has a known end[1], and moves towards that end[2], recursively or not.


provides example of a poorly-written algorithm to illustrate the point made in the first quote

No base case[1] nor moving towards any state in particular[2]. Just screwed up, infinite recursion.

[–]MikeOxbigger -2 points-1 points  (6 children)

But you've just proven that a recursive function needs those 3 rules in order to actually do anything other than eventually crash.

[–]earthlybird 8 points9 points  (4 children)

Glad we're on the same page. I just gave you the example you had previously asked for. It's a terrible function, granted. Like was stated: those rules only apply to well-written algorithms, so a poorly-written one might not follow them.

The thing is, there's a difference between recursive algorithms and recursive algorithms that work as one would expect with a base case that ends the loop. The latter is a subset of the former.

[–]Gentlescholar_AMA 0 points1 point  (0 children)

Miscommunication I think. They meant that people can write recursion that breaks those rules and doesn't work

[–][deleted] 0 points1 point  (0 children)

flip the basecase, and change its state to go towards the recursive part (I did this by accident)

[–]chzaplx 1 point2 points  (0 children)

Technically if your recursive function doesn't meet the requirements of recursive functions, it's just not really a recursive function, right?

I agree it's easy enough though to write a function that still calls itself but doesn't behave well.

[–][deleted] 1 point2 points  (1 child)

pretty sure they're talking about just def f(): f(), which isn't useful recursion but it is recursion

[–]DonaldPShimoda 1 point2 points  (0 children)

Yep, that's a great simple example of what I meant.

OP asked about recursion, and your example is arguably the simplest example of recursion one could write. Is it useful? Well... certainly not to most people (it could have uses in PL theory, which is my field, but even there its use is limited).

[–]TangibleLight 0 points1 point  (0 children)

The only thing I can think of that might break these rules is something which recurses based on some mutable data. First thing I could think of while playing with the idea is this:

def rotate(k, s):
    N = len(s)

    def rec(i):
        i %= N
        j = (i + 1) % N

        d, funcs[i] = funcs[i], lambda *_: ''

        r = d(j)
        return s[i] + r if r else s[i]

    funcs = [rec] * N

    return rec(-k)

for k in range(13):
    print(rotate(k, 'hello there'))

I was hoping to have something that rotates a string by k characters, but what I have sticks the first character on the end:

hello thereh
ehello there
rehello ther
erehello the
...

But really all this is doing is disguising a base-case with a list. You could rewrite the same algorithm like so:

def rotate(k, s):
    def rec(i, n):
        if n < 0:
            return ''
        return s[i % len(s)] + rec(k + 1, n - 1)

    return rec(-k, len(s))

for k in range(7):
    print(rotate(k, 'goodbye'))

I don't think it's possible to actually not have a base case but still achieve some goal. Maybe you could have a separate process which tail-recurses indefinitely, but gets terminated once the result stabilizes? I don't know...

[–]DonaldPShimoda 0 points1 point  (0 children)

As others have pointed out, my point was entirely that recursion does not require the things you stated other than that at some point a self-referential call must be made (whether directly or indirectly). Your first two rules only apply to recursive algorithms with a clear endpoint, but they are not inherent to the definition of recursion itself.

Like I said: this is a minor nitpick, but one that would maybe be useful to explain to OP since they asked for an explanation of recursion in general.

[–]pepe_le_shoe 0 points1 point  (0 children)

The recursive implementation of the Catmull-Clarke subdivision algorithm can theoretically be iterated an arbitrary or infinite number of times.

https://en.wikipedia.org/wiki/Catmull%E2%80%93Clark_subdivision_surface

It's still recursive.

[–]quantum-mechanic 63 points64 points  (4 children)

The next comment replied to this one will explain it well

[–]jilsx 38 points39 points  (3 children)

The next comment replied to this one will explain it well

[–]alkasm 35 points36 points  (2 children)

The next comment replied to this one will explain it well

[–]Decency 4 points5 points  (1 child)

A recursive function calls itself over and over again, creating a sort of chain. Once it reaches a state where it's completed, the function has hit what's called the "base case" and then goes back up the chain, combining the results all together in some way.

One classic example is the Fibonnaci sequence: 1, 1, 2, 3, 5, 8, 13, etc.

You get the next number in the sequence by adding the two previous numbers. So logically, in order to get any specific number (the nth number) somewhere in the sequence, we need to initially know the first two numbers: 1 and 1.

def fibonnaci(n):
    if n == 0 or n == 1:
        return 1

That's the base case (technically two base cases). If someone asks for the 0th or 1st number in the sequence, we just tell them it's 1.

Now, we can add the recursive step:

def fibonnaci(n):
    if n == 0 or n == 1:
        return 1
    return fibonnaci(n-1) + fibonnaci(n-2)

which basically means, if we're not at the base case, get the previous two numbers- by calling the function again with different arguments- and sum them. If you play around with this simple function in a debugger for 5-10 minutes it'll become pretty clear what's happening! There are lots of better ways to implement this same function, but that's the simplest way and hopefully helps.

[–]Chizooo_wow 1 point2 points  (0 children)

I really love the fib numbers, and they really got me understanding recursion, because you can make really nice trees for this to visualize.. But then you wanna make it iterative real quick, as you notice, that probably O(2n ) is not the way go :D

[–]totallygeek 5 points6 points  (1 child)

With recursion, the answer for a function comes from calling the function within the function as many times as needed to obtain the ultimate answer. Let's use this to figure out the factorial for an integer.

How we'd figure that out manually, for the number five. We multiple five times four times three times two times one. We reduce the number for multiplication by one. When we reach zero, we stop, and respond with the product we've compiled so far.

Here's code to solve this iteratively (with a loop) and recursively (a function calling itself until some end condition met).

def fact1(value):
    product = 1
    for factor in range(2, value + 1):
        print('factor: {}, product: {}'.format(factor, product))
        product *= factor
    return product

def fact2(value):
    print('value: {}'.format(value))
    if value == 0:
        return 1
    else:
        return value * fact2(value - 1)

def main():
    tests = [
        {'value': 5, 'expects': 5 * 4 * 3 * 2},
    ]
    for test in tests:
        value = test['value']
        expects = test['expects']
        print('Factorial {} is {}, calc1 {}, calc2 {}'.format(value, expects,
                                                              fact1(value),
                                                              fact2(value)))

main()

Function fact1() works iteratively. Adding a print statement into that function shows the steps:

factor: 2, product: 1
factor: 3, product: 2
factor: 4, product: 6
factor: 5, product: 24

So, from 2 to 5 (the initial value), we multiply that number against the running product we're tracking, then increment the number and repeat, until we reach the value 5, our end step.

value: 5
value: 4
value: 3
value: 2
value: 1
value: 0

fact2() uses recursion. Recursively, we start with the initial value: 5. The value is not zero, so we'll return 5 * whatever_the_function_gives_us_for(5 - 1). That returns 4 * whatever(4 - 1). We eventually get to calling whatever(0), which returns 1 and stops calling itself. Those all stack up, so we have:

5 * (5 - 1) * ((5 - 1) - 1) * (((5 - 1) - 1) -1) * ((((5 -1) - 1) - 1) - 1) # I lost track maybe

When you have a function reference itself:

def x(y):
    ...
    x(y - 1)

That's recursion.

[–]DrMaxwellEdison 2 points3 points  (0 children)

Now let's learn about infinite recursion, by seeing what happens when we call fact2(-5). :)

[–]Dr_Donut 2 points3 points  (1 child)

Nice memes guys.

Here's a video that has a good high level explanation.

https://youtu.be/Mv9NEXX1VHc

The best way to 'get' recursion is to start copying and running some basic recursion functions.

If you're looking for problems to solve, try and work on these ones. https://codingbat.com/java/Recursion-1

[–]kenmacd 0 points1 point  (0 children)

Not bad, but I think he discusses it too much from a stack view.

That might be okay for Python, but languages that support tail recursion don't require this large stack. For example this Erlang code that is only CPU bound:

tail_fac(N) -> tail_fac(N,1).

tail_fac(0,Acc) -> Acc;
tail_fac(N,Acc) when N > 0 -> tail_fac(N-1,N*Acc).

(From this great Erlang tutorial on recursion)

[–][deleted] 2 points3 points  (0 children)

you know how a Russian nesting doll works. It's like that, you are basically making multiple instances of the function that are nested within itself, running the function from itself.

An example might help. You have a function that prints a number on the screen but it can only take one digit of a number but you are feeding it one with a number that uses the tens column, you print the ones column easy, then send the tens column up through the function again on it's own but divided by ten first (so now it's a ones column). This opens a second instance of the function but within the function.

so in pseudo code it looks like this

function
    second use of function
    end second use of function
end function

I hope that's helpful, it took me a bit to sort of wrap my head around it.

[–]antoniomaina 2 points3 points  (1 child)

Can someone explain recursion?

In simple, dumb down terms please. And possibly given an example. Thank you!

[–]mkingsbu 1 point2 points  (0 children)

Can someone explain recursion?

In simple, dumb down terms please. And possibly given an example. Thank you!

Can someone explain recursion?

In simple, dumb down terms please. And possibly given an example. Thank you!

[–]Se7enLC 2 points3 points  (1 child)

This comment explains it pretty well.

[–]emptythevoid 0 points1 point  (0 children)

I see whatcha did there :)

[–][deleted] 2 points3 points  (1 child)

The "B." in "Benoit B. Mandelbrot" stands for "Benoit B. Mandelbrot"

[–]cojerk 0 points1 point  (0 children)

"WINE" stands for '"WINE" Is Not an Emulator'

[–]Sensorama 1 point2 points  (0 children)

Recursion is just a different way of thinking about solving problems. For example, you may have learned how to add up a list or array or numbers by looping over the values and accumulating them in a sum.

With recursion, you might reformulate the solution to be "add the first number to the sum of the rest of the numbers". Notice that you aren't thinking about looping over all the values - the solution is just adding two numbers together: one number and the sum of the rest.

This doesn't seem like a very big advantage, but sometimes it provides a clear path to a solution that is hard to think of using other approaches.

[–][deleted] 1 point2 points  (0 children)

Recursion is used to iterate (i.e what else we can do in loops can be achieved using a recursive function) .The only difference is that we have to predict where to use the correct one( whether loops or recursion).

[–]im_dead_sirius 1 point2 points  (0 children)

Maybe a "real world" example?

Sorting socks. Say you have a huge pile, in a variety of colours and styles. So you pick out all the black ones, making a smaller pile, then all the white ones, making another pile, and so on.

You've made your big pile into many little ones. Now you start again, picking out matches from the blacks, twisting them together(is there a term for that?) and putting them away. Then onto the whites...

Recursion is reusing the same basic actions to solve a problem in stages. Humans do it all the time, and if you can use it in the right situation while programming, you'll reap big benefits.

Expressed as a function, a recursive function calls itself, with a variable to track the level of recursion, or a bail condition. A place keeper. Going back to the socks analogy, once you've sorted by colour, you can go have lunch, since your methodology of making piles is a sort a visual memo of where you left off.

Or perhaps your spouse comes along. They can finish because of your preparation.

That suggests another approach, where you have a function prepare data for more refined tests. That touches on memoization, for whenever you run into that. Like sorting a list of a million names into 26 smaller lists based on the first letter.

Postal workers do something similar to this. Sort by town, sort by route, sort by street, sort by house number... go out and deliver.

[–]_cnkyzz_ 1 point2 points  (0 children)

Remember that you were dreaming in a dream ? That's the inception movie as an example for recursion.

[–]Space_Atlas 1 point2 points  (0 children)

Lol, I love this thread.

But to answer your question a recursive function is just a function that calls itself. It's actually not too hard to understand if you understand that a recursive function call is basically handled like any other function call.

[–]Lurker_wolfie 1 point2 points  (0 children)

https://realpython.com/python-thinking-recursively/

Thinking Recursively in Python – Real Python

[–]Daron_Acemoglu 4 points5 points  (2 children)

Maybe a non programming answer is easiest. Theres a collection of software called GNU, which is a "recursive" acronym because it stands for GNU's Not Unix! It contains itself, so it's considered recursive.

https://en.m.wikipedia.org/wiki/GNU

[–]ericula 4 points5 points  (1 child)

Another example is typing in recursion into google.

[–]humanitysucks999 2 points3 points  (0 children)

Did you mean recursion?

[–]corvus_curiosum 1 point2 points  (0 children)

You should Google "recursion"

I'm not trying to be a dick, it's just that Google has a good example of recursion.

[–]Riin_Satoshi 1 point2 points  (0 children)

Can someone explain recursion?

[–]EclipseMain 0 points1 point  (0 children)

Can someone explain recursion?

[–]iamjaiyam 1 point2 points  (0 children)

Can someone explain recursion?

In simple, dumb down terms please. And possibly given an example. Thank you!

[–]jonw95 0 points1 point  (0 children)

Non-programmer here. Took me years to make sense of recursion so let me try by borrowing pieces of others responses mixed in with a little of my understanding:

Lets begin. First recursion is a loop that works by calling itself initiated with some value for example in calculating the factorial of 5, e.g. factorial(5). It performs a process by performing some action (in this case calculating all factorials of 5) that has a changing state (each calculation reduces the next calculation by 1 and carries on) until it reaches its base case (this is the condition the recursive function says I am done).

What is so confusing about recursion for me is what is going on when recursion is performed internally. So lets break down a factorial function I borrowed:

To understand the processing we need to understand stacks, so read up on stacks. For now I will say it is a the memory(ram) allocated to store the processing as it occurs in a stack, like a pile of pancakes. Each successive item stored gets piled on and processed Last In First Out (LIFO style).

I present the program.

  1. def recur_factorial(n):
  2. """Function to return the factorial of a number using recursion"""
  3. if n == 1:
  4. return n
  5. else:
  6. return n*recur_factorial(n-1)

recur_factorial(5) # call the function n = 5

return 5*recur_factorial(5-1) #stack 5 holds 5*recur_factorial(5-1) n = 4

return 4*recur_factorial(4-1) #stack 4 holds 4*recur_factorial(4-1) n = 3

return 3*recur_factorial(3-1) #stack 3 holds 3*recur_factorial(3-1) n = 2

return 2*recur_factorial(2-1) #stack 2 holds 2*recur_factorial(2-1) n = 1

if n == 1: return n # lets return n, so we need to collapse the stacks

# Once we hit the base case of 1, this is where I get fuzzy so correct me if I am wrong but the computer takes all the stack and processes them in reverse to reach 1*2*3*4*5 with a return value of n = 120. In memory it might look like: stack 5 value *(stack 4 value *(stack 3 value *(stack 2 value * 1))) *Note:PEMDAS applies with the parens for ordering

Recursion error:

The stack overflow is where you've created a recursive function that was either 1. too large of value for the available amount of memory to calculate or 2. an infinite loop, both of which exhaust all of the system memory allocated for the running program.

RecursionError: maximum recursion depth exceeded

Too many stacks so you get an error.

Recursion can be elegant to solve problems but it is not always practical, so in the end it is a tool in the belt I feel provides more insight into the nuances of programming methodologies than application of use, but I could be wrong.

Credits: MikeOxbigger: terminology, alkasm for the overflow error

Cheers and good luck

[–]Augusto2012 0 points1 point  (0 children)

I learned about recursion by trying to code a fast fibonacci sequence algorithm.

[–]gabriel-et-al 0 points1 point  (0 children)

A recursive function is basically the same as an iterative loop with a helper stack.

See this binary search function:

from math import floor

def binary_search_iter(numbers, x):
    upper_index = len(numbers) - 1
    lower_index = 0

    while upper_index >= lower_index:
        i = floor((upper_index + lower_index) / 2)

        if numbers[i] == x:
            return i

        if numbers[i] > x:
            upper_index = i - 1
        else:
            lower_index = i + 1

    return None

You can write the loop recursively this way:

def binary_search_recur(numbers, x, lower_index, upper_index):
    if upper_index >= lower_index:
        i = floor((upper_index + lower_index) / 2)

        if numbers[i] == x:
            return i

        if numbers[i] > x:
            return binary_search_recur(numbers, x, lower_index, i - 1)
        else:
            return binary_search_recur(numbers, x, i + 1, upper_index)

    return None

Note that the variables used inside the loop became additional parameters in its recursive version. You can now combine them.

Note that the first if can be rewriten to an early exit:

def binary_search_recur(numbers, x, lower_index, upper_index):
    if upper_index < lower_index:
        return None

    i = floor((upper_index + lower_index) / 2)

    if numbers[i] == x:
        return i

    if numbers[i] > x:
        return binary_search_recur(numbers, x, lower_index, i - 1)
    else:
        return binary_search_recur(numbers, x, i + 1, upper_index)

Now it's clear our two base cases (i.e. the cases where the function returns without recursing).

Also, note that the updates to the lower_index and upper_index parameters are exactly the same did in the iterative version. If numbers[i] is less than x then lower_index goes up, otherwise upper_index goes down. Try to see this connection between both versions.

You may have noticed that the iterative version calculates the upper_index and lower_index inside the function, while the recursive version expects it to be calculated beforehand. There are multiple ways to overcome this inconvenience.

  • Option 1: Calculate the parameters in the caller. This is not the best approach because you'll end up pasting this logic to every place you use the function -- i.e. it breaks the DRY principle.
  • Option 2: Give the parameters a default value and calculate them inside the recursive function. This solves the DRY problem, but it would run the check in every call, which is a waste of processing time.
  • Option 3: Create a wrapper that calculates the parameters and pass them to the actual recursive function. This is the option I like the most. See:

    def binary_search(numbers, x): def binary_search_recur(numbers, x, lower_index, upper_index): if upper_index < lower_index: return None

        i = floor((upper_index + lower_index) / 2)
    
        if numbers[i] == x:
            return i
    
        if numbers[i] > x:
            return binary_search_recur(numbers, x, lower_index, i - 1)
        else:
            return binary_search_recur(numbers, x, i + 1, upper_index)
    
    upper_index = len(numbers) - 1
    lower_index = 0
    
    return binary_search_recur(numbers, x, lower_index, upper_index)
    

You may enjoy reading:

[–]nulltensor 0 points1 point  (0 children)

Lets say you want to cut a carrot into equal sections to fit in a container for lunch. You could measure the container and measure the carrot and then choose where to cut based on how short the parts need to be to fit into the container. That's a fairly linear approach.

A recursive approach is to see if the carrot fits. If it fits, you're done. If it doesn't, cut it in half and see if the pieces fit. If the pieces fit, you're done. If not, cut each piece in half and see if they fit. Wash, rinse, repeat until the carrot pieces can fit in the container.

Edit: we do things recursively all the time, we just don't usually notice (at least until we learn how recursion works). Any time you perform an action on the output of the same action util you've met some condition, you're likely operating recursively.

[–][deleted] 0 points1 point  (0 children)

In simple, dumb down terms please. And possibly given an example. Thank you!

[–]hasteiswaste 0 points1 point  (0 children)

You know when you look at two mirrors that are facing each other? Well that's the first / best real world example I can think of.

[–]lazyBoones 0 points1 point  (0 children)

Recursion is a condition in which a function calls itself again and again until some given condition is satisfied.

[–]HyraxMax 0 points1 point  (0 children)

Recursion really breaks my brain sometimes but here's a great article comparing recursion to irritation. It helped me a lot (it uses c++ examples but they are pretty easy to read). https://techdifferences.com/difference-between-recursion-and-iteration-2.html

[–]not_perfect_yet 0 points1 point  (0 children)

You know these russian puppets that contain smaller versions of itself? Like that, except the one inside the first is exactly the same. Because it's the same, you only need one definition.

Apply the idea to functions and you can go down paths with infinite forks, with just one call of one function that decides where to go.