## Introduction to Recursion

It’s hard to imagine computer programming without the use of recursion. It’s an essential technique that comes in handy when a function calls itself over and over again to solve a problem.

Recursive algorithms are often elegant, compact, and easy to understand once you’ve seen them in action. Many common algorithms, such as sorting and searching, use recursion in one form or another.

In this article, we’ll take a closer look at recursion, its definition, and why it’s such a valuable tool for programmers. What is Recursion?

In computer science, recursion refers to the process of a function calling itself during its execution. Recursive functions divide a problem into smaller sub-problems and solve them independently.

These sub-problems are usually identical to the original problem and can be solved using the same algorithm. The function calls itself with smaller input until the problem is trivial enough to be solved without further recursion.

Essentially, recursion is nothing more than a function calling itself until a condition is met, known as the base case. The base case is the stopping condition that prevents the function from continuing to call itself and entering an infinite loop.

It’s essential to have a base case in any recursive function; otherwise, the function calls itself forever, consuming system resources and eventually crashing. Why Use Recursion?

One reason to use recursion is that it’s an easy way to solve certain kinds of problems. In many cases, it’s more natural to define a problem recursively than iteratively.

Recursive solutions are elegant and straightforward to understand since they break a problem down into sub-problems, solve each sub-problem separately, and combine the results to get the final answer. Recursion can be more efficient than an iterative solution, especially when solving larger problems.

It may be less efficient in terms of memory usage since each recursive call creates a new stack frame. However, this trade-off is usually worth it since it leads to simpler code that’s easier to understand and debug.

## Recursion in Python

Python supports recursive functions, just like many other programming languages. In Python, when a function calls itself, a new namespace is created for that function.

This new namespace is called a local namespace and contains all the local variables and parameters for the function. A common example of recursion in Python is the factorial function.

The factorial of a given number n is defined as the product of all integers from 1 to n. The factorial function can be defined recursively as follows:

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

In this function, the base case is when n is equal to 0, and the recursive call is made with n-1.

The function calls itself with decreasing input values until it reaches the base case. Then, it returns the final result by multiplying the base case with the input value.

Get Started: Countdown to Zero

Let’s take a look at a simple example of a recursive function in Python. Consider the countdown function, which takes an integer as input and prints all the integers from the input value down to zero.

Here’s the code:

```
def countdown(n):
if n == 0:
print("Zero!")
else:
print(n)
countdown(n-1)
```

In this example, the base case is when n is equal to zero, and the function prints the message “Zero!” The recursive call is made with n-1, and the function prints the value of n during each call, going all the way down to zero.

## Non-Recursive Implementation of Countdown

We can implement the same functionality utilizing an iterative loop instead of a recursive call with similar code. Here’s a non-recursive implementation of the countdown function:

```
def countdown(n):
for i in range(n, -1, -1):
if i == 0:
print("Zero!")
else:
print(i)
```

In this implementation, we use a for loop to iterate over all the integers from n down to zero.

We print the value of i during each iteration, going all the way down to zero. Once i reaches zero, we print the message “Zero!” and exit the loop.

## Conclusion

Recursion is a powerful technique in computer programming that can simplify complex problems by breaking them down into smaller, more manageable sub-problems. The elegance and simplicity of recursive functions make them an essential tool for any programmer.

Understanding recursion, its definition, and implementation in Python can help you create efficient algorithms that solve a wide variety of problems.

## 3) Calculate Factorial

Factorial is a mathematical operation that calculates the product of all positive integers from 1 to a given number n. It’s denoted by the symbol ‘!’ and can be expressed as n! = n (n-1) (n-2) …

1. In this section, we’ll take a closer look at the factorial function, its recursive definition, recursive Python function to calculate factorial, and non-recursive implementations using for loop and reduce().

## Recursive Definition of Factorial

One way to define the factorial function is recursively. The base case for the factorial function is when n is equal to zero or one, and the factorial value is by definition 1.

The recursive case is when n is greater than one, where we multiply n by the factorial of n – 1. This gives us the formula for the factorial function:

```
n! = 1 if n = 0 or n = 1
n! = n * (n-1)! if n > 1
```

## Recursive Python Function to Calculate Factorial

We can write a recursive Python function to calculate the factorial of a given input. Here’s an example:

```
def factorial(n):
if n == 0 or n == 1:
return 1
else:
return n * factorial(n-1)
```

In this function, we use an if statement to check if n is equal to 0 or 1, which are the base cases for the factorial function.

If n is not equal to one or zero, we return n multiplied recursively by the factorial of n-1. This way, we call the function with decreasing input values until the base case is reached.

## Non-Recursive Implementations of Factorial using For Loop and Reduce()

We can also implement the factorial function using a for loop or the reduce() function. Here are two non-recursive implementations:

```
# Using For Loop
def factorial(n):
fact = 1
for i in range(1, n+1):
fact *= i
return fact
# Using Reduce Function
from functools import reduce
def factorial(n):
return reduce(lambda x, y: x * y, range(1, n+1))
```

In the first implementation, we use a for loop to iterate over all integers from 1 to n and multiply an initial value of 1 by each value in the loop. In the end, we return the final value of the product.

In the second implementation, we use the reduce() function to multiply all integers from 1 to n. This is done by passing a lambda function to the reduce() function that multiplies x by y.

## Speed Comparison of Factorial Implementations Using timeit()

To compare the speed of different factorial implementations, we can use the timeit() function in Python. Here’s an example:

```
import timeit
print(timeit.timeit(lambda: factorial(10), number=1000))
print(timeit.timeit(lambda: for_factorial(10), number=1000))
print(timeit.timeit(lambda: reduce_factorial(10), number=1000))
```

In this example, we compare the recursive factorial function with the for loop and reduce() implementations for n=10, executed 1000 times using the lambda functions. This way, we can see how long each implementation takes and determine which one is faster.

## 4) Traverse a Nested List

A nested list is a list that contains other lists as its items. Nested lists are a commonly used data structure in programming, especially when working with hierarchical data.

Traversing a nested list means visiting each item in the list, including nested lists. In this section, we’ll take a closer look at the nested list structure, problem description and algorithm using recursion, implementation of a count_leaf_items() function using recursion, and demonstration of the function on several lists.to the Nested List Structure

A nested list is a list that contains elements, which can be other lists or any other data type.

Here’s an example of a nested list:

```
nested_list = [1, [2, [3, 4], 5], 6, [7, 8]]
```

In this example, the first element of the list is an integer, but the second, fourth, and a member of the second element, 2, are nested lists.

## Problem Description and Algorithm Using Recursion

The problem we want to solve is to count the number of leaf items in a nested list. A leaf item is an item that is not a list, i.e., an integer, string, or any other data type that is not a list.

To solve this problem, we can use a recursive algorithm that traverses the nested list, and for each element in the list, we check if it’s a list or leaf item. If it’s a leaf item, we increment the leaf count.

If it’s a list, we call the function recursively with the list as the input.

## Implementation of count_leaf_items() Function Using Recursion

Here’s a Python implementation of a count_leaf_items() function that takes a nested list as input and returns the number of leaf items:

```
def count_leaf_items(nested_list):
leaf_count = 0
for item in nested_list:
if isinstance(item, list):
leaf_count += count_leaf_items(item)
else:
leaf_count += 1
return leaf_count
```

In this function, we iterate over each item in the input nested list. If the item is a list, we recursively call the function with the current item as input.

If it’s a leaf item, we increment the leaf count by one.

## Demonstration of count_leaf_items() on Several Lists

Here’s an example of how we can use the count_leaf_items() function to count the number of leaf items in a few nested lists:

```
nested_list1 = [1, [2, [3, 4], 5], 6, [7, 8]]
nested_list2 = [[1, 2], [3, 4, [5, 6]], 7, 8]
nested_list3 = [[[1, 2, 3]], [4], 5, [6, 7], [8], [[9], 10]]
print(count_leaf_items(nested_list1)) # Output: 8
print(count_leaf_items(nested_list2)) # Output: 6
print(count_leaf_items(nested_list3)) # Output: 10
```

In this example, we define three nested lists and pass each of them to the count_leaf_items() function. We print the output, which is the number of leaf items in each list.

Recursion is a fundamental technique in computer programming that helps solve complex problems by breaking them down into smaller, more manageable sub-problems. Recursive functions are elegant, easy to understand, and efficient in certain cases.

We explored the recursive definition of the factorial function, implemented recursive and non-recursive versions of the factorial and nested list traversal algorithms. Comparing the speed of these implementations, we found recursive algorithms to be slower in some cases due to the overhead of function calls.

The principle takeaway from this article is that recursive and non-recursive implementations have their advantages and disadvantages. While recursive solutions are simpler and more elegant, non-recursive solutions may be faster and more straightforward to implement.

Therefore, choosing an algorithm depends on the requirements of the problem at hand. Understanding recursion and its various applications is an asset to any programmer, allowing them to solve problems more efficiently and elegantly.