Algorithms : Functions
Algorithms : Functions
Break it down!
Introduction
In this section of the algorithms tutorial we will look at functions. Think of a function as a means to break down a bigger algorithm into a series of smaller sub algortihms.
When you get to bigger, more complex problems, utilising functions has several advantages :
- Solving a series of smaller problems is easier than trying to solve one big problem.
- Functions may be called multiple times, removing the need for duplicated code.
- Readability of the overall logic becomes easier.
In this section we will use mostly pseudocode to illustrate the concepts. We will illustrate how to implement the ideas in flowchart as well.
Functions
There are two parts to utulising a function. The first part is the function definition. This is where we include the code that makes up the function :
BEGIN functionName ( var1, var2 )
# Code goes here.
Return var
END functionName
Then we have the second part which is the function call. This is where we call the code that makes up the function :
myVar = functionName ( var3, var4 )
First off we will illustrate with a simple example that doesn't use parameters or a return value. Then we will expand the functionality, adding these back in.
Get User Input
- BEGIN
- Display "Welcome"
- getUserInput()
- Display "Now just one more question"
- getUserInput()
- END
- BEGIN getUserInput ()
- Display "We just need some more input"
- Get userInput
- Display "Thank you for that information"
- END getUserInput
The above algorithm doesn't really do anything useful. It is really just to illustrate how a function is set up and also that a function may be called just once or as many times as you like.
Parameters
A function becomes a lot more useful when we make use of parameters. Parameters are a way to pass values into the function.
This algorithm will ask for a width then print out a triangle of asterisks of that width.
Print Triangle
- BEGIN
- Get width
- Set counter to 0
- WHILE counter < width
- printLine(counter)
- Increment counter by 1
- ENDWHILE
- END
- BEGIN printLine (length)
- Display length number of "*"
- END printLine
The name of the parameter when the function is called does not have to be the same name as the name of the paramter in the function definition. They will match up with the order of how they are listed. This allows us to call the function from multiple locations, passing in different data each time.
Return Values
A function becomes even more useful when it can send back the results of its processing. This is achieved via a Return value (or values).
In the following example there are two functions, one to calculate the perimeter of a square and the other to calculate the area.
Calculate Square Details
- BEGIN
- Get width
- WHILE width > 0
- perimeter = calculate_perimeter(width)
- Display permimeter
- area = calculate_area(width)
- Display area
- Get width
- ENDWHILE
- END
- BEGIN calculate_perimeter (length)
- perimeter = length * 4
- Return perimeter
- END calculate_perimeter
- BEGIN calculate_area (length)
- area = length * length
- Return area
- END calculate_area
It is possible to combine the calculation and return lines for simple functions like these eg. "Return length * 4" combining lines 13 and 14. Generally it's better to use the approach used above however as it helps to make it more readable.
Recursion
Recursion is an interesting pattern in algorithms where a function calls itself as part of its processing. Effectively, we are solving a problem by repeatedly breaking the problem down into smaller versions of itself that branch off the earlier versions. It is a very powerful problem solving method however it can be a bit hard to get your head around at first. It is also only useful for a small subset of problems.
Let's say we want to find the Factorial of a number (that is X * (X-1) * (X - 2) ... 2)
Calculate Factorial
- BEGIN
- Get initialNumber
- result = calculte_factorial(initialNumber)
- Display result
- END
- BEGIN calculate_factorial (number)
- IF number == 2 THEN
- result = 2
- ELSE
- result = number * calculate_factorial(number - 1)
- ENDIF
- Return result
- END calculate_factorial
A recursive function must always contain a decsion which allows to break out of calling itself. Otherwise the processing would run indefinitely with no means of terminating.
An alternate approach
An alternate approach to managing return values is to set the return value (or return values) to be the last items in the parameter list.
Calculate Square Details alternate
- BEGIN
- Get width
- WHILE width > 0
- calculate_perimeter(width, perimeter)
- Display permimeter
- calculate_area(width, area)
- Display area
- Get width
- ENDWHILE
- END
- BEGIN calculate_perimeter (length, perimeter)
- perimeter = length * 4
- END calculate_perimeter
- BEGIN calculate_area (length, area)
- area = length * length
- END calculate_area
It is generally recommended that you do not use this approach however as it can be ambiguous as to which parameters are being passed in and which are return values. When writing your algorithms you should always aim for clarity and this method does not help in that regard. It is illustrated here only so that if you see it written elsewhere (in an exam for example) you will understand what is going on.
Here is the example flowchart from up above reformatted in this alternate approach.
Theory
Writing functions in your algorithms is easy. Using them to their best effect is somewhat of an art and will take some time to master.
Here are some ideas that will help you on your way to mastery.
A clean and uncluttered main function
When used well, functions can help to create a clean and uncluttered main function (sometimes referred to as a mainline). A function that acts to guide other functions that do the work and make the overall logic of the program even easier to understand. For example, this could be the main function for a simple game :
Calculate Square Details (poor use of functions)
- BEGIN
- settings = load_settings()
- play_intro()
- game = setup_game(settings)
- result = play_game(game)
- save_score(result)
- display_leaderboard()
- show_credits()
- END
Generic and Reuseable
When you create functions you should aim to make them generic and reuseable. This will allow your functions to be incorporated across more areas of processing and also to be easily reused across programs (saving you time in the long run). Once the algorithms are proven to work reliabley you will be able to drop them into future work confidently and not need to waste time retesting the logic.
There are several things you can do to help make your functions more reuseable :
- Make sure inputs are passed in as parameters as opposed to via input statements.
- Return results of processing rather than Displaying them.
- Use generic names for variables to help remove the assumption that the function may only be used for a specific task.
- Where possible, use calculations rather than hard coding values so that any input can easily be managed.
One Function, One Task
Ideally, a function should perform one task and one task only. This greatly helps with reuseability and readability. For example in the code above we could have combined calculating the area and perimeter into one function as follows :
Calculate Square Details (poor use of functions)
- BEGIN
- Get width
- WHILE width > 0
- perimeter, area = calculate_details(width)
- Display permimeter
- Display area
- Get width
- ENDWHILE
- END
- BEGIN calculate_details (length)
- perimeter = length * 4
- area = length * length
- Return perimeter, area
- END calculate_perimeter
Initially this may seem better as we have written less lines of code however this function is now less generic and less reuseable. The function performs two tasks and later on if we only needed the area, for instance, it would be ineffective. The name of the function is now also less descriptive than the two separate functions were previously and so the code also doesn't read as elegantly.
Even though it may seem like more work (and lines of code) in the short term, breaking down the processing into several smaller functions that only do one specific bit of processing will save you time in the long run and make for easier expansion of your processing when you want to add functionality.
Summary