LessonsAdvanced: Bigger programsLesson 48

Recursion

Handlers that call themselves: the base case, working through folders and nested arrays, remembering answers, and drawing a tree.

  • Advanced
  • Lesson 48 of 83
  • About 10 minutes
  • 8 examples

A handler that calls itself

Some problems contain smaller copies of themselves. The factorial of 5 (5 × 4 × 3 × 2 × 1) is 5 times the factorial of 4; a folder holds files and smaller folders, which hold files and smaller folders. A handler can solve such a problem by calling itself on the smaller copy: this is recursion.

ExampleRun it in the playground
function factorial pN
   if pN <= 1 then return 1
   return pN * factorial(pN - 1)
end factorial

put factorial(5)
put factorial(20)
Output
120
2432902008176640000

Every recursive handler has two parts: a base case, small enough to answer at once (pN <= 1), and a recursive case, which makes the problem smaller and calls itself. Each call has its own pN, so factorial(5) waits for factorial(4), which waits for factorial(3), down to the base case, and the answers come back up.

No base case, no end

Without a base case, or with one it never reaches, a handler calls itself until xTalk runs out of room and stops it with an error, on the line of the call:

A mistakeIt stops with an error on line 2
function countDown pN
   return countDown(pN - 1)
end countDown

put countDown(10)

The engine's recursionLimit property sets how deep calls may go. Raising it is rarely the fix: check that each call really makes the problem smaller, and that the base case catches it.

Thinking recursively

Recursion shines when the answer is easy to describe in terms of a smaller answer. The Tower of Hanoi moves a pile of disks from one peg to another, one disk at a time, never a big disk on a small one. Hard to plan move by move, it is three lines recursively: move all but the bottom disk out of the way, move the bottom disk, and move the others back on top of it:

ExampleRun it in the playground
local sMoves

command hanoi pDisks, pFrom, pTo, pVia
   if pDisks is 0 then exit hanoi
   hanoi pDisks - 1, pFrom, pVia, pTo
   put pFrom && "to" && pTo & return after sMoves
   hanoi pDisks - 1, pVia, pTo, pFrom
end hanoi

put empty into sMoves
hanoi 3, "A", "C", "B"
put the number of lines of sMoves && "moves:"
put line 1 to 3 of sMoves
Output
7 moves:
A to C
A to B
C to B

Walking through folders

Folders inside folders are recursion's home ground. This function lists everything in a folder, then calls itself for each folder inside, indenting a step at each level. folders() includes .., the folder above, which must be skipped, or the walk would climb back up for ever:

ExampleRun it in the playground
function listTree pFolder, pIndent
   local tList, tNames, tName
   put files(pFolder) into tNames
   sort tNames
   repeat for each line tName in tNames
      put pIndent & tName & return after tList
   end repeat
   put folders(pFolder) into tNames
   sort tNames
   repeat for each line tName in tNames
      if tName is ".." then next repeat
      put pIndent & tName & "/" & return after tList
      put listTree(pFolder & "/" & tName, pIndent & "   ") after tList
   end repeat
   return tList
end listTree

function deleteTree pFolder
   local tName, tCount
   repeat for each line tName in files(pFolder)
      delete file (pFolder & "/" & tName)
      add 1 to tCount
   end repeat
   repeat for each line tName in folders(pFolder)
      if tName is ".." then next repeat
      add deleteTree(pFolder & "/" & tName) to tCount
   end repeat
   delete folder pFolder
   return tCount
end deleteTree

put specialFolderPath("temporary") & "/oxt-lesson-tree" into tRoot
create folder tRoot
create folder (tRoot & "/docs")
create folder (tRoot & "/docs/old")
put "x" into URL ("file:" & tRoot & "/readme.txt")
put "x" into URL ("file:" & tRoot & "/docs/guide.txt")
put "x" into URL ("file:" & tRoot & "/docs/old/notes.txt")
put "x" into URL ("file:" & tRoot & "/docs/old/draft.txt")

put listTree(tRoot, "")
put deleteTree(tRoot) && "files deleted"
Output
readme.txt
docs/
   guide.txt
   old/
      draft.txt
      notes.txt

4 files deleted

The empty line comes from the list's last return. Each call works on one folder and trusts the calls it makes to deal with the folders inside: that trust is the whole trick of recursion.

Arrays inside arrays

An array's elements can be arrays themselves, to any depth (Arrays), and a recursive handler is the natural way to visit them all:

ExampleRun it in the playground
function countValues pArray
   local tKey, tCount
   if pArray is not an array then return 1
   repeat for each key tKey in pArray
      add countValues(pArray[tKey]) to tCount
   end repeat
   return tCount
end countValues

put "Ada" into tPerson["name"]
put "London" into tPerson["address"]["city"]
put "UK" into tPerson["address"]["country"]
put "maths" into tPerson["likes"][1]
put "machines" into tPerson["likes"][2]
put "poetry" into tPerson["likes"][3]
put countValues(tPerson)
Output
6

Remembering answers

A recursive handler can do the same work many times. The Fibonacci numbers (each the sum of the two before) are a famous case: fib(30) worked out plainly calls itself well over a million times, mostly to work out the same few values again. Keep each answer in an array the first time, and every later call is a lookup. This is called memoization:

ExampleRun it in the playground
local sKnown

function fib pN
   if pN < 2 then return pN
   if sKnown[pN] is empty then
      put fib(pN - 1) + fib(pN - 2) into sKnown[pN]
   end if
   return sKnown[pN]
end fib

put fib(30)
put fib(50)
Output
832040
12586269025

Drawing with recursion

A tree is a trunk with two smaller trees on top of it. Written that way, a few lines draw one: each call draws a branch, then calls itself twice for two shorter branches, turned left and right. One graphic holds every branch: in a graphic's points, an empty line lifts the pen, so the branches need not join up.

ExampleRun it in the playground
local sPoints

command branch pX, pY, pLength, pAngle, pDepth
   if pDepth is 0 then exit branch
   put pX + pLength * cos(pAngle) into tX
   put pY - pLength * sin(pAngle) into tY
   put round(pX) & comma & round(pY) & return after sPoints
   put round(tX) & comma & round(tY) & return & return after sPoints
   branch tX, tY, pLength * 0.75, pAngle + pi / 7, pDepth - 1
   branch tX, tY, pLength * 0.75, pAngle - pi / 7, pDepth - 1
end branch

put empty into sPoints
branch 240, 350, 80, pi / 2, 9
create graphic "Tree"
set the style of it to "polygon"
set the points of it to sPoints
set the foregroundColor of it to "#15803D"
set the lineSize of it to 2
put the number of lines of sPoints div 3 && "branches"
Output
511 branches
Stage what the code made, as the OXT-Beyond engine draws it
The stage, with a green tree drawn from straight lines: a trunk rising from the bottom middle that splits into two branches again and again, into a round crown of fine twigs.

Nine levels of two calls each make 29 − 1 = 511 branches. Change the angle, the 0.75 or the depth, and the tree changes shape.

Try it yourself

Write a recursive function reverseText that reverses text: the reverse of a text is the reverse of everything after its first character, followed by the first character. What is the base case? Reverse stressed.

Show a solution

The base case is text of one character or none, which is its own reverse.

ExampleRun it in the playground
function reverseText pText
   if the length of pText <= 1 then return pText
   return reverseText(char 2 to -1 of pText) & char 1 of pText
end reverseText

put reverseText("stressed")
put reverseText("racecar")
Output
desserts
racecar

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.