Tutorial 10

✏️

Functions & Procedures - Custom

9618 A-Level


The previous tutorial looked at the built-in Cambridge modules/sub-routines (functions or procedures) - in this tutorial, we'll look at how to create and call (use) our own

A recap, the difference between a function and procedure is outlined below:

  • Functions will return a value that can be used to assign, in a calculation, a condition, output statement etc
  • Procedures won't return a value - we can output data inside the procedure, but the procedure itself won't evaluate to an answer

The benefits of creating functions and procedures include:

  • Organisation - keeps code organised into small, logical blocks
  • Reusability - don't have to copy and paste same functionality over and over
  • Flexibility - if we need to modify behaviour, we only need to change once inside the module
  • Testing - easy to test individual modules to ensure they work
  • Parameterisation - can use the same module with different parameters to achieve different behaviour

1

FUNCTIONs

Each time we declare a function, we need:

  • FUNCTION & ENDFUNCTION keywords
  • An identifier (function name)
  • [optional] A list of parameters
  • A return data type
  • A return statement for every code path

A simple example is shown below that takes in an integer parameter, then returns whether it is a lucky number, according to some superstition:

Note: when specifying the return type in the function header, we use the keyword RETURNS, while when actually returning a value, we use RETURN

OUTPUT IsLuckyNumber(7) OUTPUT IsLuckyNumber(8) FUNCTION IsLuckyNumber(num : INTEGER) RETURNS BOOLEAN IF num = 7 OR num = 4 THEN RETURN TRUE ENDIF RETURN FALSE ENDFUNCTION

Note: the following example would be invalid - since not all situations (code paths) result in a value being returned

OUTPUT IsLuckyNumber(7) OUTPUT IsLuckyNumber(8) FUNCTION IsLuckyNumber(num : INTEGER) RETURNS BOOLEAN IF num = 7 OR num = 4 THEN RETURN TRUE ENDIF //we need to return something if the number isn't 7 or 4 ENDFUNCTION

Note: the order of execution demonstrated via these OUTPUT statements - when we return from a function, we leave it and execution continues from the position the function was called from - this is why the OUTPUT "Leaving Function" statement will never execute

OUTPUT "-- Before Try 7-- " OUTPUT IsLuckyNumber(7) OUTPUT "-- Before Try 8 ---" OUTPUT IsLuckyNumber(8) OUTPUT "--- Finished ---" FUNCTION IsLuckyNumber(num : INTEGER) RETURNS BOOLEAN OUTPUT "In Function" IF num = 7 OR num = 4 THEN RETURN TRUE ENDIF RETURN FALSE OUTPUT "Leaving Function" // will never execute, since we have already returned ENDFUNCTION

2

PROCEDUREs

Declaring a procedure requires less code than a function, since we don't have to return anything - we simply need:

  • PROCEDURE & ENDPROCEDURE keywords
  • An identifier (procedure name)
  • [optional] A list of parameters

Note: in order to call (use) a procedure, we require the CALL keyword

CALL Welcome("Alice") CALL Welcome("Bob") PROCEDURE Welcome(name : STRING) OUTPUT "Welcome to pseudocode.pro, ", name ENDPROCEDURE

3

Multiple Parameters

For either functions or procedures, we can either write each parameter and its data type individually, or if we have consecutive parameters of the same type, we can simply separate them with a comma - like the "name" and "email" parameters below:

CALL DisplayDetails("Claire", "claire123@proton.me", 25, TRUE) PROCEDURE DisplayDetails(name, email : STRING, age : INTEGER, paid : BOOLEAN) OUTPUT name, " is ", age, " years old, has the email address ", email, " and their paid status is ", paid ENDPROCEDURE

4

BYVAL vs BYREF

One of the trickier concepts in the syllabus is the way parameters are passed - there are two options:

  • BYVAL: this is the default parameter passing method if none is specified. A copy of the parameter is copied when the module is called and this copy is used throughout the module - i.e. any changes we make to this parameter won't affect the original variable
  • BYREF: by reference. A reference (memory address) to the original variable is passed - i.e. any changes we make to this parameter will affect the original variable

Note in the example below that, the value inside the procedure will double, but the original x variable won't

DECLARE x : INTEGER x ← 123 OUTPUT x CALL DoubleNum(x) OUTPUT "--- After Procedure ---" OUTPUT x PROCEDURE DoubleNum(BYVAL num : INTEGER) //since BYVAL is the default, it's technically not required OUTPUT "--- In Procedure ---" num ← num * 2 OUTPUT num ENDPROCEDURE

In the following example, since we are using BYREF, then a copy of the variable isn't created inside the procedure - x and num are pointing to the same memory address, so modifying num inside the procedure will also modify the value of x

DECLARE x : INTEGER x ← 123 OUTPUT x CALL DoubleNum(x) OUTPUT "--- After Procedure ---" OUTPUT x PROCEDURE DoubleNum(BYREF num : INTEGER) OUTPUT "--- In Procedure ---" num ← num * 2 OUTPUT num ENDPROCEDURE

5

Triangle

Create a procedure that can output a right-angled triangle to a given depth - for example, if the depth was 4, the result would be:

CALL DrawTriangle(10) PROCEDURE DrawTriangle(depth: INTEGER) DECLARE line : STRING DECLARE numStarsForThisLine, starNum : INTEGER FOR numStarsForThisLine ← 1 TO depth line ← "" FOR starNum ← 1 TO numStarsForThisLine line ← line & '*' NEXT starNum OUTPUT line NEXT numStarsForThisLine ENDPROCEDURE

6

Hash Length

There are various online tools that can take a hash digest (string), then based on its value, try to predict the hashing algorithm that we used - one simple approach is by the length, since most hashing algorithms produced fixed-length hashes

Create a function that takes a string hash digest and returns the name of the possible hashing algorithm used

The data below shows the hashing algorithm and length (in characters) of the digest it produces:

  • md5: 32
  • sha256: 64
  • sha512: 128

If the length doesn't match one of these value, then [UNKNOWN] should be returned

You can use this site to hash various values to use as arguments for your function - e.g. "hello world" hashes to "5eb63bbbe01eeed093cb22bb8f5acdc3"

OUTPUT PredictHashAlgorithm("5eb63bbbe01eeed093cb22bb8f5acdc3") OUTPUT PredictHashAlgorithm("abc") FUNCTION PredictHashAlgorithm(digest : STRING) RETURNS STRING CASE OF LENGTH(digest) 32: RETURN "md5" 64: RETURN "sha256" 128: RETURN "sha512" OTHERWISE: RETURN "[UNKNOWN]" ENDCASE ENDFUNCTION

7

Is Even

Create a function that returns a Boolean value corresponding to whether an integer parameter is even or not

OUTPUT IsEven(4) OUTPUT IsEven(5) FUNCTION IsEven(num : INTEGER) RETURNS BOOLEAN RETURN MOD(num, 2) = 0 ENDFUNCTION

8

Is Prime

Create a function that returns a Boolean value corresponding to whether an integer parameter is prime or not

Use this function in a loop to output all the primes between 1 and 1000

DECLARE n : INTEGER FOR n ← 1 TO 1000 IF IsPrime(n) THEN OUTPUT n ENDIF NEXT n FUNCTION IsPrime(num : INTEGER) RETURNS BOOLEAN DECLARE max, index : INTEGER max ← DIV(num, 2) IF num <= 1 THEN RETURN FALSE ENDIF FOR index ← 2 TO max IF MOD(num, index) = 0 THEN RETURN FALSE ENDIF NEXT index RETURN TRUE ENDFUNCTION

9

Swap Case

Create a function that takes a string then swaps the case of each character - e.g. "psEudoCOdE" should become "PSeUDOcoDe"

OUTPUT SwapCase("ascii UNICODE") FUNCTION SwapCase(orig : STRING) RETURNS STRING DECLARE newStr : STRING DECLARE index : INTEGER DECLARE currentChar : CHAR newStr ← "" FOR index ← 1 TO LENGTH(orig) currentChar ← MID(orig, index, 1) IF currentChar >= 'a' AND currentChar <= 'z' THEN newStr ← newStr & UCASE(currentChar) ELSE IF currentChar >= 'A' AND currentChar <= 'Z' THEN newStr ← newStr & LCASE(currentChar) ELSE newStr ← newStr & currentChar ENDIF ENDIF NEXT index RETURN newStr ENDFUNCTION

10

Times Table

Create a procedure that outputs the times table of a given number from a start to ending multiplier - for example CALL DisplayTimesTable(6, 10, 20) would output:

CALL DisplayTimesTable(6, 10, 20) PROCEDURE DisplayTimesTable(timesTable, start, end : INTEGER) DECLARE index : INTEGER FOR index ← start TO end OUTPUT index, " x ", timesTable, " = ", (index * timesTable) NEXT index ENDPROCEDURE

11

Absolute Value

Create a function that takes a number then returns its absolute value

OUTPUT abs(-5) OUTPUT abs(6) FUNCTION abs(num : REAL) RETURNS REAL IF num >= 0 THEN RETURN num ELSE RETURN -num ENDIF ENDFUNCTION

12

Factorial

Create a function that can return the factorial of a given number - i.e. factorial of 5 is 5 * 4 * 3 * 2 * 1 = 120

OUTPUT factorial(99) FUNCTION factorial(max : INTEGER) RETURNS INTEGER DECLARE num, total : INTEGER total ← 1 FOR num ← max TO 1 STEP -1 total ← total * num NEXT num RETURN total ENDFUNCTION

13

Round

Create a function that takes a real number then rounds it to the nearest integer

DECLARE n : REAL FOR n ← 1 TO 2 STEP 0.1 OUTPUT n, " rounds to ", Round(n) NEXT n FUNCTION Round(n : REAL) RETURNS INTEGER DECLARE floor : INTEGER floor ← INT(n) IF n - floor >= 0.5 THEN RETURN INT(n + 0.5) ELSE RETURN floor ENDIF ENDFUNCTION

14

Add Days

Create a function that takes in a date and number of days to add, then returns a new date - e.g. adding 5 days to 30/12/2024 would result in the new date 04/01/2025

Note: for simplicity, you can ignore leap years

OUTPUT AddDays(30/12/2024, 10000) FUNCTION AddDays(origDate : DATE, daysToAdd : INTEGER) RETURNS DATE DECLARE daysInMonth : ARRAY[1:12] OF INTEGER DECLARE d, m, y : INTEGER daysInMonth ← [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] d ← DAY(origDate) m ← MONTH(origDate) y ← YEAR(origDate) WHILE daysToAdd > 0 DO IF daysToAdd + d > daysInMonth[m] THEN daysToAdd ← daysToAdd - (daysInMonth[m] - d) d ← 0 m ← m + 1 IF m > 12 THEN m ← 1 y ← y + 1 ENDIF ELSE d ← d + daysToAdd daysToAdd ← 0 ENDIF ENDWHILE RETURN SETDATE(d, m, y) ENDFUNCTION

15

Double Array

Create a procedure that takes an array then doubles all of its values - i.e. the array [1, 2, 3] should become [2, 4, 6]

DECLARE numbers : ARRAY[1:5] OF INTEGER DECLARE index : INTEGER numbers ← [5, 18, 42, 6, 29] CALL Double(numbers) FOR index ← 1 TO LENGTH(numbers) OUTPUT numbers[index] NEXT index PROCEDURE Double(BYREF arr : ARRAY OF INTEGER) DECLARE index : INTEGER FOR index ← 1 TO LENGTH(arr) numbers[index] ← numbers[index] * 2 NEXT index ENDPROCEDURE

16

Random Files

Create a procedure that asks the user how many files they'd like to create, how many random numbers should be in each file, the min & max value of the random numbers and once complete, if they'd like to output any file

For example, if they choose to create 3 files with 5 numbers each, then the files "numbers1.txt" to "numbers3.txt" would be created

CALL CreateRandomFiles() PROCEDURE CreateRandomFiles() DECLARE numFiles, min, max, amountNumbers, chosenFileNum, fileNum, index : INTEGER DECLARE filename, line : STRING OUTPUT "How many files would you like to create?" INPUT numFiles OUTPUT "What should be the minimum number generated?" INPUT min OUTPUT "What should be the maximum number generated?" INPUT max OUTPUT "How many numbers should be generated?" INPUT amountNumbers FOR fileNum ← 1 TO numFiles filename ← "numbers" & NUM_TO_STR(fileNum) & ".txt" OPENFILE filename FOR WRITE FOR index ← 1 TO amountNumbers WRITEFILE filename, min + RAND(max - min) NEXT index CLOSEFILE filename NEXT fileNum OUTPUT "Choose file number to output (1 to ", numFiles, "):" INPUT chosenFileNum IF chosenFileNum >= 1 AND chosenFileNum <= numFiles THEN filename ← "numbers" & NUM_TO_STR(chosenFileNum) & ".txt" OPENFILE filename FOR READ WHILE NOT EOF(filename) DO READFILE filename, line OUTPUT line ENDWHILE CLOSEFILE filename ELSE OUTPUT "Invalid file num. Exiting..." ENDIF ENDPROCEDURE

17

Character Type

Create a function that takes in a character then returns either "uppercase", "lowercase", "number" or "other" depending on the character's type

OUTPUT CharacterType('A') OUTPUT CharacterType('t') OUTPUT CharacterType('5') OUTPUT CharacterType('!') FUNCTION CharacterType(c : CHAR) RETURNS STRING CASE OF ASC(c) 65 TO 90 : RETURN "uppercase" 97 TO 122 : RETURN "lowercase" 48 TO 57 : RETURN "number" OTHERWISE : RETURN "other" ENDCASE ENDFUNCTION

18

Is Weekend

Create a function that returns a Boolean corresponding to whether a given date is a weekend (Saturday or Sunday)

OUTPUT IsWeekend(01/03/2025) OUTPUT IsWeekend(02/03/2025) OUTPUT IsWeekend(03/03/2025) FUNCTION IsWeekend(d : DATE) RETURNS BOOLEAN DECLARE dayNum : INTEGER dayNum ← DAYINDEX(d) IF dayNum = 1 OR dayNum = 7 THEN RETURN TRUE ELSE RETURN FALSE ENDIF ENDFUNCTION

19

Credit Card Numbers

Create a procedure that can take a credit card number as a string, then output the following information:

  • Card Network: Visa cards start with a 4; Mastercards start with a 5
  • Bank Identification Number: digits 2 to 6 identify the bank
  • Account: digits 7 to 15 represent your bank account
  • Check digit: digit 16

Note: both Visa & Mastercard cards should be 16 digits - an error should be output if the card number isn't this length

Example cards can be found here - any non-numbers (spaces, hyphens etc) should be removed

As an extension, you could also implement the relatively simple Luhn algorithm that credit cards use - to verify the check digit is correct

CALL CardDetails("4567-8901-2345 6789") PROCEDURE CardDetails(origCardNum : STRING) DECLARE cardNum : STRING DECLARE index : INTEGER DECLARE currentChar : CHAR cardNum ← "" FOR index ← 1 TO LENGTH(origCardNum) currentChar ← MID(origCardNum, index, 1) IF IS_NUM(currentChar) THEN cardNum ← cardNum & currentChar ENDIF NEXT index IF LENGTH(cardNum) = 16 THEN IF LEFT(cardNum, 1) = "4" THEN OUTPUT "Card Network: Visa" ELSE IF LEFT(cardNum, 1) = "5" THEN OUTPUT "Card Network: Mastercard" ELSE OUTPUT "[Unknown Card Network]" ENDIF ENDIF OUTPUT "BIN: ", MID(cardNum, 2, 5) OUTPUT "Account: ", MID(cardNum, 7, 8) OUTPUT "Check Digit: ", RIGHT(cardNum, 1) ELSE OUTPUT "Unrecognised card format - Visa & Mastercard should be 16 digits" ENDIF ENDPROCEDURE

20

Add Numbers in a String

Create a function to add all the digits in a string - for example, "h3l10 w0r1d" would return 5, since 3 + 1 + 0 + 0 + 1 = 5

OUTPUT AddNumbersInString("h3l10 w0r1d") FUNCTION AddNumbersInString(str : STRING) RETURNS INTEGER DECLARE index, total : INTEGER DECLARE currentChar : CHAR total ← 0 FOR index ← 1 TO LENGTH(str) currentChar ← MID(str, index, 1) IF IS_NUM(currentChar) THEN total ← total + STR_TO_NUM(currentChar) ENDIF NEXT index RETURN total ENDFUNCTION

21

Friday 13th

Many people consider the date Friday 13th to be unlucky/haunted - create a procedure that will output a message depending on whether today is/isn't Friday 13th

CALL IsFriday13th() PROCEDURE IsFriday13th() DECLARE currentDate : DATE currentDate ← TODAY() //can test with 13/06/2025 for a valid Friday 13th IF DAY(currentDate) = 13 AND DAYINDEX(currentDate) = 6 THEN OUTPUT "Today is Friday 13th...be careful" ELSE OUTPUT "You're ok..." ENDIF ENDPROCEDURE

22

Hash Code

Create a function that takes a string, adds up the ASCII values of all of its characters combines, then finds the remainder after dividing by 100 to get the hash code

OUTPUT HashCode("hello") FUNCTION HashCode(str : STRING) RETURNS INTEGER DECLARE index, total : INTEGER DECLARE currentChar : CHAR total ← 0 FOR index ← 1 TO LENGTH(str) currentChar ← MID(str, index, 1) total ← total + ASC(currentChar) NEXT index RETURN total MOD 100 ENDFUNCTION

23

Starts & Ends With Vowel

Create a function that takes a string, then capitalises any word that both starts and ends with a vowel - for example "ace pilot" would return "ACE pilot"

OUTPUT CapitaliseStartEndVowels("ada lovelace") FUNCTION CapitaliseStartEndVowels(sentence : STRING) RETURNS STRING DECLARE newSentence, currentWord : STRING DECLARE isWord : BOOLEAN DECLARE index : INTEGER DECLARE currentChar : CHAR sentence ← TO_LOWER(sentence) newSentence ← "" currentWord ← "" FOR index ← 1 TO LENGTH(sentence) currentChar ← MID(sentence, index, 1) IF currentChar >= 'a' AND currentChar <= 'z' THEN isWord ← TRUE currentWord ← currentWord & currentChar ELSE IF isWord = TRUE THEN IF IsVowel(LEFT(currentWord, 1)) AND IsVowel(RIGHT(currentWord, 1)) THEN newSentence ← newSentence & TO_UPPER(currentWord) ELSE newSentence ← newSentence & currentWord ENDIF currentWord ← "" ENDIF isWord ← FALSE newSentence ← newSentence & currentChar ENDIF NEXT index IF isWord = TRUE THEN IF IsVowel(LEFT(currentWord, 1)) AND IsVowel(RIGHT(currentWord, 1)) THEN newSentence ← newSentence & TO_UPPER(currentWord) ELSE newSentence ← newSentence & currentWord ENDIF ENDIF RETURN newSentence ENDFUNCTION FUNCTION IsVowel(c : CHAR) RETURNS BOOLEAN CASE OF c 'a', 'e', 'i', 'o', 'u' : RETURN TRUE OTHERWISE: RETURN FALSE ENDCASE ENDFUNCTION

24

Random Characters

Create a procedure that outputs a given number of random characters between a min and max ASCII (well technically Unicode) value - for example, CALL RandomChars(5, 128000, 130000) might output:

CALL RandomChars(10, 128000, 130000) PROCEDURE RandomChars(amount, min, max : INTEGER) DECLARE index, randNum : INTEGER FOR index ← 1 TO amount randNum ← min + INT(RAND(max - min + 1)) OUTPUT randNum, ": ", CHR(randNum) NEXT index ENDPROCEDURE