Книга: A Common-Sense Guide to Data Structures and Algorithms in Python, Volume 1
Назад: Chapter 11: Learning to Write in Recursive
Дальше: Recursive Category: Calculations

Recursive Category: Repeatedly Execute

Over the course of tackling various recursive problems, I began to find that there are various categories of problems. Once I learned an effective technique for a certain category, when I found another problem that belonged to the same category, I was able to apply the same technique to solve it.

The category that I found to be the easiest was one in which the goal of the algorithm was to repeatedly execute a task.

The NASA spacecraft countdown algorithm from the previous chapter is a great example. The code prints a number such as 10, and then 9, then 8, all the way down to 0. While the number the function prints is different each time, we boil down the code’s essence to the fact that it is repeatedly executing a task—namely, the printing of a number.

This was our implementation of that algorithm:

 def​ ​countdown​(number):
 print​(number)
 
 if​ number == 0:
 return
 else​:
  countdown(number - 1)

I found that for problems of this category, the last line of code in the function was a simple, single call to the function again. In the previous snippet, this takes the form of countdown(number - 1). This line does one thing: it makes the next recursive call.

The directory-printing algorithm from the previous chapter is another example of this. This function repeatedly executes the task of printing directory names.

Our code looked like this:

 import​ ​os
 
 
 def​ ​print_subdirectories​(directory_name):
 for​ filename ​in​ os.listdir(directory_name):
  path = os.path.join(directory_name, filename)
 if​ os.path.isdir(path):
 print​(path)
  print_subdirectories(path)

Here as well, the last line of code is print_subdirectories(path), which is a simple call to the recursive function, triggering it again.

Recursive Trick: Passing Extra Parameters

Let’s try our hand at another problem of the “repeatedly execute” category. We’re going to write an algorithm that takes an array of numbers and doubles each of the numbers within the array. Note that we’re not going to produce a new array; instead, we’re going to modify the array in place.

This algorithm, too, is one in which we repeatedly execute a task. Specifically, we repeatedly double a number. We start with the first number and double it. We move onto the second number and double it—and so on.

Let’s take a stab at writing this algorithm in Python, which we’ll call double_array(). We know that our final line will be the recursive call, so let’s include that line:

 def​ ​double_array​(array):
  double_array(array)

 

Next, we need to add the code that will actually double the number. But which number will we double? Let’s try to double the first number:

 def​ ​double_array​(array):
  array[0] *= 2
  double_array(array)

Okay, so we’ve doubled the number at index 0, but how do we proceed to double the number at index 1?

Now, if we were to use a loop instead of recursion, we would have used a variable to keep track of the index and continuously increased it by 1, something along these lines:

 def​ ​double_array​(array):
  index = 0
 
 while​ index < len(array):
  array[index] *= 2
  index += 1

In our recursive version, though, the only argument to our function is the array. We need some way to keep track of and increment an index. How do we pull this off?

And now for our next trick…

Let’s pass in extra parameters!

Let’s modify the beginning of our function so that it accepts two arguments—the array itself and an index to keep track of. Here’s the code:

 def​ ​double_array​(array, index):

As things stand right now, when we call this function, we need to pass in the array and the starting index, which will be 0:

 double_array([1, 2, 3, 4, 5], 0)

Once we have the index as a function argument, we have a way of incrementing and tracking the index as we make each successive recursive call. Here’s the code for this:

 def​ ​double_array​(array, index):
  array[index] *= 2
  double_array(array, index + 1)

In each successive call, we pass in the array again as the first argument, but we also pass along an incremented index. This allows us to keep track of an index just as we would in a classical loop.

Our code isn’t perfect just yet, though. Our function will throw an error once the index goes past the end of the array and tries to multiply a nonexistent number. To solve this, we need our base case:

 def​ ​double_array​(array, index):
 # Base case: when the index goes past the end of the array
 if​ index >= len(array):
 return
 
  array[index] *= 2
  double_array(array, index + 1)

We can test this function out with the following code:

 array = [1, 2, 3, 4]
 double_array(array, 0)
 print​(array)

Our recursive function is now complete. However, if our programming language supports default arguments as Python does, we can make things even prettier.

Right now, we need to call the function like this:

 double_array([1, 2, 3, 4, 5], 0)

Admittedly, passing in that 0 as a second parameter isn’t beautiful—it’s just so we can achieve our trick of maintaining an index. After all, we always want to start our index off at 0.

However, we can use default parameters to allow us to simply call the function the original way:

 double_array([1, 2, 3, 4, 5])

Here’s our updated code to make this work:

 def​ ​double_array​(array, index=0):
 # Base case: when the index goes past the end of the array
 if​ index >= len(array):
 return
 
  array[index] *= 2
  double_array(array, index + 1)

All we updated here was setting a default argument of index=0. This way, the first time we call the function, we don’t have to pass in the index parameter. However, we still get to use the index parameter for all successive calls.

The “trick” of using extra function parameters is a common technique in writing recursive functions, and a handy one.

Назад: Chapter 11: Learning to Write in Recursive
Дальше: Recursive Category: Calculations