Recursion: Wikis

Advertisements
  
  
  

Note: Many of our articles have direct quotes from sources you can cite, within the Wikipedia article! This article doesn't yet, but we're working on it! See more info or our list of citable articles.

Encyclopedia

(Redirected to Recursion article)

From Wikipedia, the free encyclopedia

A visual form of recursion known as the Droste effect. The woman in this image is holding an object which contains a smaller image of her holding the same object, which in turn contains a smaller image of herself holding the same object, and so forth.

Recursion, in mathematics and computer science, is a method of defining functions in which the function being defined is applied within its own definition; specifically it is defining an infinite statement using finite components. The term is also used more generally to describe a process of repeating objects in a self-similar way. For instance, when the surfaces of two mirrors are exactly parallel with each other the nested images that occur are a form of infinite recursion.

Contents

Formal definitions of recursion

Recursion in a VLC program on a pc

In mathematics and computer science, a class of objects or methods exhibit recursive behavior when they can be defined by two properties:

  1. A simple base case (or cases), and
  2. A set of rules which reduce all other cases toward the base case.

For example, the following is a recursive definition of a person's ancestors:

  • One's parents are one's ancestors (base case).
  • The parents of one's ancestors are also one's ancestors (recursion step).

The Fibonacci sequence is a classic example of recursion:

  • Fib(0) is 0 [base case]
  • Fib(1) is 1 [base case]
  • For all integers n > 1: Fib(n) is (Fib(n-1) + Fib(n-2)) [recursive definition]

A convenient mental model of recursion defines the recursive object (whether that object is an equation, an algorithm, an image, or a rule) in terms of "previously defined" objects of the same class. For example: How do you move a stack of 100 boxes? Answer: you move one box, remember where you put it, and then solve the smaller problem: how do you move a stack of 99 boxes? Eventually, you're left with the problem of how to move a single box, which you know how to do.

Many mathematical axioms are based upon recursive rules. For example, the formal definition of the natural numbers in set theory follows: 0 is a natural number, and each natural number has a successor, which is also a natural number. By this base case and recursive rule, one can generate the set of all natural numbers

A more humorous illustration goes: "To understand recursion, one must first understand recursion." Or perhaps more accurate is the following, from Andrew Plotkin: "If you already know what recursion is, just remember the answer. Otherwise, find someone who is standing closer to Douglas Hofstadter than you are; then ask him or her what recursion is."

Recursively defined mathematical objects include functions, sets, and especially fractals.

Recursion in language

The use of recursion in linguistics, and the use of recursion in general, dates back to the ancient Indian linguist Pāṇini in the 5th century BC, who made use of recursion in his grammar rules of Sanskrit.

Linguist Noam Chomsky theorizes that unlimited extension of a language such as English is possible only by the recursive device of embedding sentences in sentences. Thus, a chatty person may say, "Dorothy, who met the wicked Witch of the West in Munchkin Land where her wicked Witch sister was killed, liquidated her with a pail of water." Clearly, two simple sentences—"Dorothy met the Wicked Witch of the West in Munchkin Land" and "Her sister was killed in Munchkin Land"—can be embedded in a third sentence, "Dorothy liquidated her with a pail of water," to obtain a very verbose sentence.

However, if "Dorothy met the Wicked Witch" can be analyzed as a simple sentence, then the recursive sentence "She lived in the house Jack built" could be analyzed that way too, if "Jack built" is analyzed as an adjective, "Jack-built", that applies to the house in the same way "Wicked" applies to the Witch. "She lived in the Jack-built house" is unusual, perhaps poetic sounding, but it is not clearly wrong.

The idea that recursion is the essential property that enables language is challenged by linguist Daniel Everett in his work Cultural Constraints on Grammar and Cognition in Pirahã: Another Look at the Design Features of Human Language in which he hypothesizes that cultural factors made recursion unnecessary in the development of the Pirahã language. This concept challenges Chomsky's idea that recursion is the only trait which differentiates human and animal communication and is currently under intense debate.

Recursion in linguistics enables 'discrete infinity' by embedding phrases within phrases of the same type in a hierarchical structure. Without recursion, language does not have 'discrete infinity' and cannot embed sentences into infinity (with a 'Russian doll' effect). Everett contests that language must have discrete infinity, and that the Pirahã language - which he claims lacks recursion - is in fact finite. He likens it to the finite game of chess, which has a finite number of moves but is nevertheless very productive, with novel moves being discovered throughout history.

Advertisements

Recursion in plain English

Recursion is the process a procedure goes through when one of the steps of the procedure involves rerunning the procedure. A procedure that goes through recursion is said to be recursive. Something is also said to be recursive when it is the result of a recursive procedure.

To understand recursion, one must recognize the distinction between a procedure and the running of a procedure. A procedure is a set of steps that are to be taken based on a set of rules. The running of a procedure involves actually following the rules and performing the steps. An analogy might be that a procedure is like a menu in that it is the possible steps, while running a procedure is actually choosing the courses for the meal from the menu.

A procedure is recursive if one of the steps that makes up the procedure calls for a new running of the procedure. Therefore, a recursive four-course meal would be a meal in which one of the choices of appetizer, salad, entrée, or dessert was an entire meal unto itself. So a recursive meal might be potato skins, baby greens salad, chicken Parmesan, and for dessert, a four-course meal, consisting of crab cakes, Caesar salad, for an entrée, a four-course meal, and chocolate cake for dessert, so on until each of the meals within the meals is completed.

A recursive procedure must complete every one of its steps. Even if a new running is called in one of its steps, each running must run through the remaining steps. What this means is that even if the salad is an entire four-course meal unto itself, you still have to eat your entrée and dessert.

Recursive humor

A common joke (for example recursion in the Jargon File) is the following "definition" of recursion.

Recursion
See "Recursion".

(Obviously, this recursion lacks the base case, so it will recur indefinitely.)

This is a parody on references in dictionaries, which in some cases may lead to circular definitions among related words. Jokes often have an element of wisdom, and also an element of misunderstanding. This one is also the shortest possible example of an erroneous recursive definition of an object, the error being the absence of the termination condition (or lack of the initial state, if looked at from an opposite point of view). Newcomers to recursion are often bewildered by its apparent circularity, until they learn to appreciate that a termination condition is key.

An example of this can be found by searching Google for the term "Recursion". Google cleverly puts the searcher in an endless cycle, of suggesting "Recursion" as the word they were trying to spell, even though that's exactly the input they just used.

A variation is:

Recursion
If you still don't get it, see: "Recursion".

which actually does terminate, as soon as the reader "gets it."

Another example occurs in Kernighan and Ritchie's "The C Programming Language." The following index entry is found on page 269:

recursion 86, 139, 141, 182, 202, 269

Other examples are recursive acronyms, such as GNU, PHP, YAML or HURD.

Some subtle jokes in mathematical recursive definitions include:

Factorial function seen in The Register defined as
fact(∞) -> ∞
fact(N) -> fact(N+1)/(N+1)
Fibonacci series
fib(∞) -> ∞
fib(N) -> fib(N+2) - fib(N+1)

Recursion in mathematics

A Sierpinski triangle—a confined recursion of triangles to form a geometric lattice.

Recursively defined sets

Example: the natural numbers

The canonical example of a recursively defined set is given by the natural numbers:

0 is in \mathbb{N}
if n is in \mathbb{N}, then n + 1 is in \mathbb{N}
The set of natural numbers is the smallest set of real numbers satisfying the previous two properties.

(The doubt with this definition is that we assume: 1.we understand the "+" operation and 2.n + 1 is not in current \mathbb{N}. These two assumptions mean that before we understand the natural numbers, we already know the "+" operation on them.)

Example: The set of true reachable propositions

Another interesting example is the set of all true "reachable" propositions in an axiomatic system.

  • if a proposition is an axiom, it is a true reachable proposition.
  • if a proposition can be obtained from true reachable propositions by means of inference rules, it is a true reachable proposition.
  • The set of true reachable propositions is the smallest set of reachable propositions satisfying these conditions.

This set is called 'true reachable propositions' because: in non-constructive approaches to the foundations of mathematics, the set of true propositions is larger than the set recursively constructed from the axioms and rules of inference. See also Gödel's incompleteness theorems.

(Note that determining whether a certain object is in a recursively defined set is not an algorithmic task.)

Functional recursion

A function may be partly defined in terms of itself. A familiar example is the Fibonacci number sequence: F(n) = F(n − 1) + F(n − 2). For such a definition to be useful, it must lead to values which are non-recursively defined, in this case F(0) = 0 and F(1) = 1.

A famous recursive function is the Ackermann function which, unlike the Fibonacci sequence, cannot be expressed without recursion.

Recursive proofs

Applying the standard technique of proof by cases to recursively-defined sets or functions as in the preceding sections yields structural induction, a powerful generalization of mathematical induction which is widely used to derive proofs in mathematical logic and computer science.

For instance, the standard way to define new systems of mathematics or logic is to define objects (such as "true" and "false", or "all natural numbers"), then define operations on these. These are the base cases. After this, all valid computations in the system are defined with rules for assembling these. In this way, if the base cases and rules are all proven to be calculable, then any formula in the mathematical system will also be calculable.

While the above example may seem unexciting, this type of proof is the normal way to prove that a calculation is impossible. This can often save a lot of time. For example, this type of proof was used to prove that the area of a circle is not a simple ratio of its diameter, and that no angle can be trisected with compass and straightedge—both puzzles that fascinated the ancients.

Recursive optimization

Dynamic programming is an approach to optimization which restates a multiperiod or multistep optimization problem in recursive form. The key result in dynamic programming is the Bellman equation, which writes the value of the optimization problem at an earlier time (or earlier step) in terms of its value at a later time (or later step).

Recursion in computer science

A common method of simplification is to divide a problem into subproblems of the same type. As a computer programming technique, this is called divide and conquer and is key to the design of many important algorithms. Divide and conquer serves as a top-down approach to problem solving, where problems are solved by solving smaller and smaller instances. A contrary approach is dynamic programming. This approach serves as a bottom-up approach, where problems are solved by solving larger and larger instances, until the desired size is reached.

A classic example of recursion is the definition of the factorial function, given here in C code:

 unsigned int factorial(unsigned int n) 
 {
     if (n <= 1) {
          return 1;
     } else {
          return n * factorial(n-1);
     }
 }

The function calls itself recursively on a smaller version of the input (n - 1) and multiplies the result of the recursive call by n, until reaching the base case, analogously to the mathematical definition of factorial.

Recursion in computer programming is exemplified when a function is defined in terms of simpler, often smaller versions of itself. The solution to the problem is then devised by combining the solutions obtained from the simpler versions of the problem. One example application of recursion is in parsers for programming languages. The great advantage of recursion is that an infinite set of possible sentences, designs or other data can be defined, parsed or produced by a finite computer program.

Recurrence relations are equations to define one or more sequences recursively. Some specific kinds of recurrence relation can be "solved" to obtain a non-recursive definition.

Use of recursion in an algorithm has both advantages and disadvantages. The main advantage is usually simplicity. The main disadvantage is often that the algorithm may require large amounts of memory if the depth of the recursion is very large.

The recursion theorem

In set theory, this is a theorem guaranteeing that recursively defined functions exist. Given a set X, an element a of X and a function f: X \rightarrow X, the theorem states that there is a unique function F: N \rightarrow X (where N denotes the set of natural numbers including zero) such that

F(0) = a
F(n + 1) = f(F(n))

for any natural number n.

Proof of uniqueness

Take two functions F and G of domain N and codomain A such that:

F(0) = a
G(0) = a
F(n + 1) = f(F(n))
G(n + 1) = f(G(n))

where a is an element of A. We want to prove that F = G. Two functions are equal if they:

i. have equal domains/codomains;
ii. have the same graphic.
i. :ii. Mathematical induction: for all n in N, F(n) = G(n)? (We shall call this condition, say, Eq(n)):
1.Eq(0) if and only if F(0) = G(0) if and only if a = a.
2.Let n be an element of N. Assuming that Eq(n) holds, we want to show that Eq(n + 1) holds as well, which is easy because: F(n + 1) = f(F(n)) = f(G(n)) = G(n + 1).

Examples

  • See Hungerford, "Algebra", first chapter on set theory.

Some common recurrence relations are:

See also

References

  • Johnsonbaugh, Richard (2004). Discrete Mathematics. Prentice Hall. ISBN 0-13-117686-2. 
  • Hofstadter, Douglas (1999). Gödel, Escher, Bach: an Eternal Golden Braid. Basic Books. ISBN 0-465-02656-7. 
  • Shoenfield, Joseph R. (2000). Recursion Theory. A K Peters Ltd. ISBN 1-56881-149-7. 
  • Causey, Robert L. (2001). Logic, Sets, and Recursion. Jones & Bartlett. ISBN 0-7637-1695-2. 
  • Cori, Rene; Lascar, Daniel; Pelletier, Donald H. (2001). Recursion Theory, Godel's Theorems, Set Theory, Model Theory. Oxford University Press. ISBN 0-19-850050-5. 
  • Barwise, Jon; Moss, Lawrence S. (1996). Vicious Circles. Stanford Univ Center for the Study of Language and Information. ISBN 0-19-850050-5.  - offers a treatment of corecursion.
  • Rosen, Kenneth H. (2002). Discrete Mathematics and Its Applications. McGraw-Hill College. ISBN 0-07-293033-0. 
  • Cormen, Thomas H., Charles E. Leiserson, Ronald L. Rivest, Clifford Stein (2001). Introduction to Algorithms. Mit Pr. ISBN 0-262-03293-7. 
  • Kernighan, B.; Ritchie, D. (1988). The C programming Language. Prentice Hall. ISBN 0-13-110362-8. 
  • Stokey, Nancy,; Robert Lucas; Edward Prescott (1989). Recursive Methods in Economic Dynamics. Harvard University Press. ISBN 0674750969. 

External links


Wikibooks

Up to date as of January 23, 2010
(Redirected to F Sharp Programming/Recursion article)

From Wikibooks, the open-content textbooks collection

< F Sharp Programming
Previous: Pattern Matching Basics Index Next: Higher Order Functions
F# : Recursion and Recursive Functions


A recursive function is a function which calls itself. Interestingly, in contrast to many other languages, functions in F# are not recursive by default. A programmer needs to explicitly mark a function as recursive using the rec keyword:

let rec someFunction = ...

Contents

Examples

Factorial in F#

The factorial of a non-negative integer n, denoted by n!, is the product of all positive integers less than or equal to n. For example, 6! = 6 * 5 * 4 * 3 * 2 * 1 = 720.

In mathematics, the factorial is defined as follows:

 fact(n) = \begin{cases} 1 & \mbox{if } n = 0 \ n \times fact(n-1) & \mbox{if } n > 0 \ \end{cases}

Naturally, we'd calculate a factorial by hand using the following:

fact(6) =
        = 6 * fact(6 - 1)
        = 6 * 5 * fact(5 - 1)
        = 6 * 5 * 4 * fact(4 - 1)
        = 6 * 5 * 4 * 3 * fact(3 - 1)
        = 6 * 5 * 4 * 3 * 2 * fact(2 - 1)
        = 6 * 5 * 4 * 3 * 2 * 1 * fact(1 - 1)
        = 6 * 5 * 4 * 3 * 2 * 1 * 1
        = 720

In F#, the factorial function can be written concisely as follows:

let rec fact x =
    if x < 1 then 1
    else x * fact (x - 1)

Here's a complete program:

open System
 
let rec fact x =
    if x < 1 then 1
    else x * fact (x - 1)
 
(* // can also be written using pattern matching syntax:
let rec fact = function
    | 0 | 1 -> 1
    | n -> n * fact (n - 1) *)
 
Console.WriteLine(fact 6)

Greatest Common Divisor (GCD)

The greatest common divisor, or GCD function, calculates the largest integer number which evenly divides two other integers. For example, largest number that evenly divides 259 and 111 is 37, denoted GCD(259, 111) = 37.

Euclid discovered a remarkably simple recursive algorithm for calculating the GCD of two numbers:

 gcd(x,y) = \begin{cases} x & \mbox{if } y = 0 \ gcd(y, remainder(x,y)) & \mbox{if } x >= y \mbox{ and } y > 0 \ \end{cases}

To calculate this by hand, we'd write:

gcd(259, 111)   = gcd(111, 259 % 111)
                = gcd(111, 37)
                = gcd(37, 0)
                = 37

In F#, we can use the % (modulus) operator to calculate the remainder of two numbers, so naturally we can define the GCD function in F# as follows:

open System
 
let rec gcd x y =
    if y = 0 then x
    else gcd y (x % y)
 
Console.WriteLine(gcd 259 111) // prints 37

Tail Recursion

Let's say we have a function A which, at some point, calls function B. When B finishes executing, the CPU must continue executing A from the point where it left off. To "remember" where to return, the function A passes a return address as an extra argument to B on the stack; B jumps back to the return address when it finishes executing. This means calling a function, even one that doesn't take any parameters, consumes stack space, and its extremely easy for a recursive function to consume all of the available memory on the stack.

A tail recursive function is a special case of recursion in which the last instruction executed in the method is the recursive call. F# and many other functional languages can optimize tail recursive functions; since no extra work is performed after the recursive call, there is no need for the function to remember where it came from, and hence no reason to allocate additional memory on the stack.

F# optimizes tail-recursive functions by telling the CLR to drop the current stack frame before executing the target function. As a result, tail-recursive functions can recurse indefinitely without consuming stack space.

Here's non-tail recursive function:

> let rec count n =
    if n = 1000000 then
        printfn "done"
    else
        if n % 1000 = 0 then
            printfn "n: %i" n
 
        count (n + 1) (* recursive call *)
        () (* <-- This function is not tail recursive
              because it performs extra work (by
              returning unit) after
              the recursive call is invoked. *);;
 
val count : int -> unit
 
> count 0;;
n: 0
n: 1000
n: 2000
n: 3000
...
n: 58000
n: 59000
Session termination detected. Press Enter to restart.
Process is terminated due to StackOverflowException.

Let's see what happens if we make the function properly tail-recursive:

> let rec count n =
    if n = 1000000 then
        printfn "done"
    else
        if n % 1000 = 0 then
            printfn "n: %i" n
 
        count (n + 1) (* recursive call *);;
 
val count : int -> unit
 
> count 0;;
n: 0
n: 1000
n: 2000
n: 3000
n: 4000
...
n: 995000
n: 996000
n: 997000
n: 998000
n: 999000
done

If there was no check for n = 1000000, the function would run indefinitely. Its important to ensure that all recursive function have a base case to ensure they terminate eventually.

How to Write Tail-Recursive Functions

Let's imagine that, for our own amusement, we wanted to implement a multiplication function in terms of the more fundamental function of addition. For example, we know that 6 * 4 is the same as 6 + 6 + 6 + 6, or more generally we can define multiplication recursively as M(a, b) = a + M(a, b - 1), b > 1. In F#, we'd write this function as:

let rec slowMultiply a b =
    if b > 1 then
        a + slowMultiply a (b - 1)
    else
        a

It may not be immediately obvious, but this function is not tail recursive. It might be more obvious if we rewrote the function as follows:

let rec slowMultiply a b =
    if b > 1 then
        let intermediate = slowMultiply a (b - 1) (* recursion *)
        let result = a + intermediate (* <-- additional operations *)
        result                   
    else a

Since the slowMultiply function isn't tail recursive, it throws a StackOverFlowException for inputs which result in very deep recursion:

> let rec slowMultiply a b =
    if b > 1 then
        a + slowMultiply a (b - 1)
    else
        a;;
 
val slowMultiply : int -> int -> int
 
> slowMultiply 3 9;;
val it : int = 27
 
> slowMultiply 2 14;;
val it : int = 28
 
> slowMultiply 1 100000;;
 
Process is terminated due to StackOverflowException.
Session termination detected. Press Enter to restart.

Its possible to re-write most recursive functions into their tail-recursive forms using an accumulating parameter:

> let slowMultiply a b =
    let rec loop acc counter =
        if counter > 1 then
            loop (acc + a) (counter - 1) (* tail recursive *)
        else
            acc
    loop a b;;
 
val slowMultiply : int -> int -> int
 
> slowMultiply 3 9;;
val it : int = 27
 
> slowMultiply 2 14;;
val it : int = 28
 
> slowMultiply 1 100000;;
val it : int = 100000

The accumulator parameter in the inner loop holds the state our function throughout each recursive iteration.

Exercises

Solutions.

Faster Fib Function

The following function calculates the nth number in the Fibonacci sequence:

let rec fib = function
    | 0I -> 0I
    | 1I -> 1I
    | n -> fib(n - 1I) + fib(n - 2I)
Note: The function above has the type val fib : bigint -> bigint. Previously, we've been using the int or System.Int32 type to represent numbers, but this type has a maximum value of 2,147,483,647. The type bigint is used for arbitrary size integers such as integers with billions of digits. The maximum value of bigint is constrained only by the available memory on a users machine, but for most practical computing purposes we can say this type is boundless.

The function above is neither tail-recursive nor particularly efficient with a computational complexity O(2n). The tail-recursive form of this function has a computational complexity of O(n). Re-write the function above so that its tail recursive.

You can verify the correctness of your function using the following:

fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(10) = 55
fib(100) = 354224848179261915075

Additional Reading

Previous: Pattern Matching Basics Index Next: Higher Order Functions

Simple English

[[File:|thumb|right|A visual form of recursion is the Droste effect. It leads to self-similar images.]] Recursion is a word from mathematics. In mathematics and in computer science, it is used to define a thing, usually a function. Unlike with normal definitions, the function to be defined can be used to define itself.

Recursion consists of two steps:

  1. Are we done yet? If so, return the results. Without this step, a recursion would go on forever.
  2. If not, recurse: simplify the problem, solve the simpler problem(s), and assemble the results into a solution for the original problem. Then return that solution.

An example might be how to define ancestor, using recursion:

  1. A person's parents are his or her ancestors (base hypothesis)
  2. The ancestors of a person's ancestors are also ancestors of the person being considered (recursion step).

For the real explanation see Recursion.


Advertisements






Got something to say? Make a comment.
Your name
Your email address
Message