Recursion

We are no longer in the Google Python Course.

Recursion allows you to define infinite objects, finitely

We may define the positive integers as follows:

The number 1 is a positive integer.

Any positive integer plus 1 is a positive integer.

Below we define a generator for integers. (We will discuss generators later.)

These examples are found in the python file recursion.py

We may extend the definition to include all integers.

Any positive integer is an integer.

Any integer minus 1 is an integer.

Recursive Data Structures

Lists and strings are recursive data structures.

The empty list [] is a list.

Appending an item to a list results in a list.

Similarly for strings.

The empty string "" is a string.

Concatenating a character to a string results in a string.

We will see similar definitions for other data structures, including stacks, queues, trees, and graphs. FYI, the Facebook friends network is a graph.

Given a recursive data structure, it is convenient to use recursive functions to process the recursive structures. The two hallmarks of a recursive function are

A base case, such as the empty list or the empty string.

A traversal function for moving through the data structure.

Below we compare recursion to iteration and introduce tail recursion as a more efficient version.

Iterative versions of total [sum], length (len), and max.

Recursive versions of total [sum], length [len], and max

Below we define trace() which allows us watch the execution of recursive functions. Later we will see that trace() can be used as a decorator.

Tail Recursion

As should be apparent from the traces of the recursive functions above, there is a lot of overhead for calling recursive functions. The state of the calling function has to be pushed on the stack, waiting for the recursive calls to complete.

One way to avoid this overhead is to write tail recursive functions. That is, the function is recursive, but the calling function does not need to wait to complete the final value. The tail recursive call includes the needed result values.

See (https://en.wikipedia.org/wiki/Tail_call)

Here are revised versions of the tail recursive functions that do not require helper functions. They use default parameter values instead.

Let's try tracing these tail recursive functions.

Oops. We have a problem. The trace() function is assuming that the parameter function has a single parameter itself. The tr--best functions have a variable number of arguments due to the default parameter. We need to modify trace to handle the variable number of arguments.

The sad fact is that python does not optimize tail-recursive calls. See (https://chrispenner.ca/posts/python-tail-recursion). As the linked page shows, you can implement tail recursion, creating a Recurse class. We will revisit this topic later.

More recursive functions

Here are a couple of additional recursive functions. These examples are related to the homework problems.

Context Free Grammars

Above we discussed the use of recursion in providing succinct definitions of mathematical objects, like integers, and data structures, like lists.

Another important use of recursion is defining grammars for languages - both natural languages, like English and German, and computer languages, like Python and C.

You are no doubt familiar with parts of speech such as noun, verb, and prepositions. These are the non-terminal building blocks of a grammar. The terminal elements are words: nouns such as "cat" or "dog", and verbs such as "ran" or "chased".

Below is a simple grammar (in racket) for a trivial subset of English.

(define grammar-mcd
  (cfg
   '(a the mouse cat dog it slept swam chased evaded dreamed believed that)
   '(s np vp det n pn vi vt v3)
   's
   (list
    (rule 's '(np vp))
    (rule 'np '(det n))
    (rule 'np '(pn))
    (rule 'det '(a))
    (rule 'det '(the))
    (rule 'n '(mouse))
    (rule 'n '(cat))
    (rule 'n '(dog))
    (rule 'pn '(it))
    (rule 'vp '(vi))
    (rule 'vp '(vt np))
    (rule 'vp '(v3 that s))
    (rule 'vi '(slept))
    (rule 'vi '(swam))
    (rule 'vt '(chased))
    (rule 'vt '(evaded))
    (rule 'v3 '(dreamed))
    (rule 'v3 '(believed)))))

We interpret the grammar as follows:

Terminal values: a the mouse cat dog it slept swam chased evaded dreamed believed that

Non-terminal values: s np vp det n pn vi vt v3

Rewrite rules: (the left side can be rewritten as the right side)

s : np vp   # a sentence is a noun phrase followed by a verb phrase
np : det n  # a noun phrase can be a determiner followed by a noun
np : pn     # a noun can be a pronoun
det : a     # a determiner can be a
det : the   # a determiner can be the
n : mouse   # a noun can be mouse
n : cat     # a noun can be cat
n : dog     # a noun can be dog
pn : it     # a pronoun can be it
vp : vi     # a verb phrase can be an intransitive verb
vp : vt np  # a verb phrase can be a transitive verb followed by a noun phrase
vp : v3 that s # a verb phrase can be a v3 verb followed by "that" followed by a sentence
vi : slept  # an intransitive verb can be slept
vi : swam   # an intransitive verb can be swam
vt : chased # a transitive verb can be chased
vt : evaded # a transitive verb can be evaded
v3: dreamed # a v3 verb can be dreamed
v3 : believed # a v3 verb can be believed

We can derive a sentence by expanding each non-terminal node of a tree until no non-terminal nodes remain. This is a recursive process.

S -> NP VP
NP -> DET N
DET -> the
N -> cat
VP -> VT NP
VT -> chased
NP -> PN
PN -> it

the cat chased it

Note that the v3 rule results in a recursive call to the sentence node.

This type of grammar, with a single non-terminal on the left hand side, is known as a context free grammar. There is a more restrictive type of grammar known as regular expressions, which we will examine later.

There are also less restrictive grammars including context sensitive grammars and recursively enumerable languages. We will not be discussing those.

They all fall under the general category of formal languages.

Context free grammars are especially interesting for computer science as they provide a grammatical structure for most computer programming languages. The common name for these grammatical descriptions of programming languages is Backus Naur Form or BNF. John Backus designed FORTRAN and Peter Naur designed ALGOL.

See http://matt.might.net/articles/grammars-bnf-ebnf/ for a discussion of the language of languages.

End of Recursion notebook, for now. Later we will discuss deep recursion.