How this works

Choose a question, write your solution in C or C++17, run the supplied tests, then mark it complete. Difficulty is shown by the dots: intro, core, advanced, brutal.

Run entirely in your browser

Edit the starter, provide input and run the supplied tests without installing anything. Compilation happens locally in your browser.

Open compiler

1. Pick a question

Open any question and choose Open in compiler. C is the default; use the arrow beside it to choose C++17. Your last choice is remembered in this browser.

2. Write and test

Fill in the starter’s TODO, run it with your own input, then use Run tests to check every supplied case. When it passes, use the question’s checkbox to remember your progress.

Work locally instead

Use the download button in the Questions panel, then unzip it anywhere. If you filter the questions first, the download includes only the questions currently shown.

If ./autotest.sh says Permission denied, run this once:

SH
chmod +x autotest.sh

Download a question’s prac_qN.c file, save it beside autotest.sh, and fill in the // TODO. Keep the filename and the supplied main unchanged.

SH
./autotest.sh 1        # check question 1
./autotest.sh 1 5 12   # check several
./autotest.sh          # check every question in the folder

The script compiles your file and reports failing inputs, compile errors, crashes and timeouts. It needs bash and a C compiler. Linux, macOS and CSE work; on Windows, use WSL or a CSE machine.

Safety and page controls

The readable script uses no network access, installs nothing and never needs sudo. Nothing runs until you start it. Press Ctrl + K for page shortcuts, and use each question’s checkbox to remember your progress in this browser.

Question 1Count Negative Numbers

(2 marks)

Estimated time: 10-15 minutes

A finance application processes an array of transaction amounts (where negative values represent withdrawals) and wants a quick summary of how many withdrawals occurred. Write a function that counts how many elements of an array of n integers are strictly negative.

Zero should not be counted as negative, since it represents neither a deposit nor a withdrawal.

Your task is to complete the function int count_negative(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q1.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q1.c:

TERMINAL
gcc -Wall -Wextra -o prac_q1 prac_q1.c
./prac_q1
6
-3 5 -1 0 8 -9
3

./prac_q1
3
1 2 3
0

./prac_q1
0
0

Assumptions / Restrictions / Clarifications

  • n may be 0, in which case the function should return 0.
  • Zero is not considered negative and must not be counted.
  • arr may contain any mixture of negative, zero and positive values.
  • Do not call scanf, getchar or fgets inside count_negative; all input is provided by main via arr and n.
  • Do not print anything inside count_negative; only main should print.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • array traversal with an index loop
  • comparing elements against a constant
  • accumulating a running count

Worked example

For example:

For the array -3 5 -1 0 8 -9:

  • -3 is negative, so it counts.
  • 5 is positive, does not count.
  • -1 is negative, so it counts.
  • 0 is not negative (it is neither positive nor negative), does not count.
  • 8 is positive, does not count.
  • -9 is negative, so it counts.

There are 3 negative elements in total, so count_negative returns 3.

Edge cases to consider

  • n is 0 (empty array) so the loop body never runs and the answer is 0
  • an array containing 0s, which must not be counted
  • an array where every element is negative
  • an array with no negatives at all

Common mistakes

  • counting 0 as negative by using <= 0 instead of < 0
  • starting the counter at 1 instead of 0
  • looping to i <= n and reading one element past the end

Optional extension challenge

Also return the sum of the negative values via a second int pointer parameter, without changing the return type of the count.

You can re-fetch the starter code for this question: prac_q1.c.

Question 2Count Uppercase Letters

(2 marks)

Estimated time: 10-15 minutes

A password-strength checker wants to verify that a candidate password contains a reasonable number of uppercase letters. Write a function that counts the number of uppercase alphabetic characters in a null-terminated string.

The function should examine every character in the string once and only count letters in the range 'A' to 'Z'.

Your task is to complete the function int count_upper(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q2.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q2.c:

TERMINAL
gcc -Wall -Wextra -o prac_q2 prac_q2.c
./prac_q2
Hello World
2

./prac_q2
no capitals here
0

./prac_q2
SHOUTING
8

Assumptions / Restrictions / Clarifications

  • Non-alphabetic characters (digits, spaces, punctuation) should be ignored.
  • Lowercase letters should not be counted.
  • The string may be empty, in which case the function returns 0.
  • Do not call scanf, getchar or fgets inside count_upper; the string is passed in as an argument.
  • count_upper must not modify the string it is given.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • iterating a null-terminated string
  • character range comparisons ('A'..'Z')
  • read-only use of a string argument

Worked example

For example:

For the string "Hello World":

  • 'H' is uppercase, so it counts.
  • 'ello ' are all lowercase or a space, none count.
  • 'W' is uppercase, so it counts.
  • 'orld' are all lowercase, none count.

There are 2 uppercase letters in total, so count_upper returns 2.

Edge cases to consider

  • the empty string "", which returns 0
  • a string with no uppercase letters
  • a string that is ALL uppercase
  • strings containing digits, spaces and punctuation that must be ignored

Common mistakes

  • using the loop condition i < strlen(s) recomputed every iteration instead of s[i] != '\0'
  • accidentally counting lowercase letters by comparing against 'a'..'z'
  • using ctype without including <ctype.h>, or writing the range test back-to-front

Optional extension challenge

Return the count of uppercase letters minus lowercase letters to measure the 'shoutiness' of the text.

You can re-fetch the starter code for this question: prac_q2.c.

Question 3Count Occurrences of a Character

(2 marks)

Estimated time: 10-15 minutes

A text-analysis tool wants to know how frequently a particular character (for example a punctuation mark or a specific letter) appears within a block of text. Write a function that counts how many times a given character occurs within a null-terminated string.

The function should scan the entire string once and tally every exact match of the target character.

Your task is to complete the function int count_char(char *s, char target), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q3.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q3.c:

TERMINAL
gcc -Wall -Wextra -o prac_q3 prac_q3.c
./prac_q3
banana
a
3

./prac_q3
banana
z
0

./prac_q3
Banana
B
1

Assumptions / Restrictions / Clarifications

  • Comparison is case-sensitive, so 'a' and 'A' are considered different characters.
  • The string may be empty, in which case the function returns 0.
  • target may be any valid char, including the space character or a digit.
  • Do not call scanf, getchar or fgets inside count_char; both arguments are passed in directly.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • string traversal
  • exact character equality
  • passing a char by value

Worked example

For example:

For the string "banana" and target 'a':

  • the letters at positions 1, 3 and 5 (0-indexed) are 'a', 'a', 'a'.
  • every other letter ('b', 'n', 'n') is not 'a'.

There are 3 occurrences of 'a', so count_char returns 3.

Edge cases to consider

  • the empty string, returning 0
  • target that does not appear at all
  • counting the space character or a digit as the target
  • a target that appears at the very first and very last position

Common mistakes

  • doing a case-insensitive compare when the spec requires case-sensitive
  • comparing s[i] to the whole target string instead of a single char
  • stopping at the first match instead of counting all matches

Optional extension challenge

Make the comparison case-insensitive by adding an int flag parameter, treating 'a' and 'A' as equal only when the flag is set.

You can re-fetch the starter code for this question: prac_q3.c.

Question 4String Length Without strlen

(2 marks)

Estimated time: 10-15 minutes

As an exercise in understanding how C strings work under the hood, you are to write your own version of the standard library's strlen function, which returns the number of characters in a null-terminated string, not counting the null terminator itself.

You may not call the real strlen (or any other library function that computes string length) to implement this; you must count the characters yourself by scanning until the null terminator.

Your task is to complete the function int my_strlen(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q4.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q4.c:

TERMINAL
gcc -Wall -Wextra -o prac_q4 prac_q4.c
./prac_q4
hello
5

./prac_q4

0

./prac_q4
a b c
5

Assumptions / Restrictions / Clarifications

  • The string may be empty, in which case the function returns 0.
  • s is always a valid, non-NULL, null-terminated string.
  • The count does not include the null terminator character itself.
  • You must not call strlen (or any equivalent library function) inside my_strlen.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • understanding the null terminator
  • manual string traversal
  • returning a computed length

Worked example

For example:

For the string "a b c":

  • the characters are 'a', ' ', 'b', ' ', 'c' -- five characters in total, including the spaces.
  • the scan stops as soon as the null terminator '\0' after 'c' is reached.

my_strlen therefore returns 5.

Edge cases to consider

  • the empty string, whose length is 0
  • a single-character string
  • a string containing spaces (which are part of the length)

Common mistakes

  • counting the '\0' itself in the length
  • returning the loop index off by one
  • assuming a fixed buffer size instead of stopping at '\0'

Optional extension challenge

Write a companion that returns the length up to the first space, or the full length if there is no space.

You can re-fetch the starter code for this question: prac_q4.c.

Question 5Swap Two Integers via Pointers

(2 marks)

Estimated time: 10-15 minutes

As part of learning how pointers let a function modify variables in the caller's scope, you are to write a small utility that exchanges the values of two integer variables. Write a function that swaps the values pointed to by two int pointers.

Because C passes arguments by value, the function must take pointers to the two integers so that it can modify the caller's original variables rather than local copies.

Your task is to complete the function void swap_ints(int *a, int *b), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q5.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q5.c:

TERMINAL
gcc -Wall -Wextra -o prac_q5 prac_q5.c
./prac_q5
3 8
8 3

./prac_q5
-1 -9
-9 -1

./prac_q5
5 5
5 5

Assumptions / Restrictions / Clarifications

  • a and b are always valid, non-NULL pointers to int.
  • a and b may point to the same address, in which case the values should be unchanged after the call.
  • The values pointed to may be negative, zero, or positive.
  • swap_ints must not return a value; it communicates entirely through the pointers it is given.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • pointer dereferencing
  • modifying caller variables through pointers
  • using a temporary variable

Worked example

For example:

For x = 3 and y = 8:

  • before the call, x holds 3 and y holds 8.
  • swap_ints(&x, &y) copies *a (3) into a temporary, copies *b (8) into *a, then copies the temporary (3) into *b.
  • after the call, x holds 8 and y holds 3.

main prints "8 3".

Edge cases to consider

  • swapping a value with itself (both pointers refer to equal values)
  • negative values
  • the two values already being in the desired order

Common mistakes

  • swapping the pointers locally instead of the values they point to
  • losing a value by assigning before saving it in a temporary
  • forgetting to dereference (*a vs a)

Optional extension challenge

Write a three-way rotate function rotate3(int *a, int *b, int *c) that moves a->b->c->a using only pointer dereferences.

You can re-fetch the starter code for this question: prac_q5.c.

Question 6Largest of Three

(2 marks)

Estimated time: 10-15 minutes

A simple scoring system needs to determine the winning score out of three competitors in a round. Write a function that takes three integers and returns the largest of them.

Your function should handle any combination of positive, negative and equal values correctly.

Your task is to complete the function int largest_of_three(int a, int b, int c), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q6.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q6.c:

TERMINAL
gcc -Wall -Wextra -o prac_q6 prac_q6.c
./prac_q6
3 7 5
7

./prac_q6
-1 -1 -2
-1

./prac_q6
4 4 4
4

Assumptions / Restrictions / Clarifications

  • a, b and c may be any valid int value, including negative numbers.
  • If two or more values are tied for largest, return that value (ties do not matter).
  • You should not use any library sorting or max functions; a few comparisons are enough.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • boolean/relational logic
  • chained comparisons or nested if
  • returning the correct branch value

Worked example

For example:

For a = 3, b = 7, c = 5:

  • largest starts as a = 3.
  • b = 7 is greater than 3, so largest becomes 7.
  • c = 5 is not greater than 7, so largest stays 7.

largest_of_three returns 7.

Edge cases to consider

  • all three values equal
  • two of the three tied for the maximum
  • negative values only
  • the maximum being the first, middle, or last argument

Common mistakes

  • using && / || logic that misses the case where two values tie
  • returning a comparison result (0 or 1) instead of the actual largest value
  • forgetting one of the three orderings

Optional extension challenge

Generalise to return the largest of an int array of length n instead of exactly three fixed arguments.

You can re-fetch the starter code for this question: prac_q6.c.

Question 7Sum of Digits

(2 marks)

Estimated time: 10-15 minutes

A checksum utility needs to compute a simple digit sum of a number as part of validating identifiers. Write a function that returns the sum of the decimal digits of a non-negative integer. For example the digits of 4325 are 4, 3, 2 and 5, which sum to 14.

Your function should work using arithmetic (division and modulo) rather than converting the number to a string.

Your task is to complete the function int sum_of_digits(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q7.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q7.c:

TERMINAL
gcc -Wall -Wextra -o prac_q7 prac_q7.c
./prac_q7
4325
14

./prac_q7
7
7

./prac_q7
0
0

./prac_q7
1000
1

Assumptions / Restrictions / Clarifications

  • n is always non-negative.
  • sum_of_digits(0) should return 0.
  • n may have any number of digits that fits within a normal int.
  • You should not use sprintf or any string conversion to solve this.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • integer division and modulo
  • loop until a value becomes 0
  • digit extraction with % 10 and / 10

Worked example

For example:

For n = 4325:

  • 4325 % 10 = 5, sum becomes 5, n becomes 432.
  • 432 % 10 = 2, sum becomes 7, n becomes 43.
  • 43 % 10 = 3, sum becomes 10, n becomes 4.
  • 4 % 10 = 4, sum becomes 14, n becomes 0, loop stops.

sum_of_digits returns 14.

Edge cases to consider

  • the number 0, whose digit sum is 0
  • a single-digit number
  • a number ending in one or more zeros

Common mistakes

  • infinite loop from forgetting the / 10 step
  • using % 10 but never dividing, so only the last digit is summed

Optional extension challenge

Return the repeated digital root (keep summing digits until a single digit remains) instead of a single pass.

You can re-fetch the starter code for this question: prac_q7.c.

Question 8Array Contains Value

(2 marks)

Estimated time: 10-15 minutes

A ticket booking system keeps a list of already-booked seat numbers in an array and needs a quick way to check whether a particular seat number has already been taken. Write a function that returns 1 if a given target value appears anywhere in an array of n integers, and 0 otherwise.

The array is not assumed to be sorted, so the search should check elements in order until either a match is found or the whole array has been examined.

Your task is to complete the function int array_contains(int arr[], int n, int target), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q8.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q8.c:

TERMINAL
gcc -Wall -Wextra -o prac_q8 prac_q8.c
./prac_q8
6
4 8 15 16 23 42
15
1

./prac_q8
6
4 8 15 16 23 42
7
0

./prac_q8
0
5
0

Assumptions / Restrictions / Clarifications

  • n may be 0, in which case the function should return 0.
  • arr may contain duplicate values.
  • target may or may not appear in arr, and may be any valid int value.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • linear search
  • early return on a match
  • returning a 0/1 truth value

Worked example

For example:

For arr = [4, 8, 15, 16, 23, 42] and target = 15:

  • 4, 8 are checked and do not match.
  • 15 is checked and matches target.

The search stops as soon as the match is found, so array_contains returns 1.

Edge cases to consider

  • empty array (n == 0), always returns 0
  • target at the first position
  • target at the last position
  • target appearing more than once

Common mistakes

  • returning 1 unconditionally after the loop
  • not returning early and overwriting a found result with a later miss
  • off-by-one reading past the end of the array

Optional extension challenge

Return the index of the first match (or -1 if absent) instead of a boolean, without scanning the array twice.

You can re-fetch the starter code for this question: prac_q8.c.

Question 9Temperature Converter

(2 marks)

Estimated time: 10-15 minutes

Weather stations around the country record temperatures in degrees Celsius, but an international reporting system your team is building needs everything converted to degrees Fahrenheit before it is displayed. Write a function that converts a temperature given in degrees Celsius into degrees Fahrenheit. The formula for the conversion is F = C * 9 / 5 + 32.

Your function should work correctly for any valid temperature, including negative values (below freezing) and fractional values (e.g. 36.6 degrees for a body temperature reading).

Your task is to complete the function double celsius_to_fahrenheit(double celsius), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q9.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q9.c:

TERMINAL
gcc -Wall -Wextra -o prac_q9 prac_q9.c
./prac_q9
0.0
32

./prac_q9
100.0
212

./prac_q9
-40.0
-40

./prac_q9
36.6
97.88

Assumptions / Restrictions / Clarifications

  • You may assume celsius is a valid finite double.
  • Do not round the result; return the exact computed value.
  • celsius may be negative, zero, or positive, and may be a fractional value.
  • You do not need to validate that celsius is within any particular physical range.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • floating-point arithmetic
  • operator precedence in a formula
  • returning a double

Worked example

For example:

For celsius = 36.6:

  • F = 36.6 * 9 / 5 + 32.
  • 36.6 * 9 = 329.4, and 329.4 / 5 = 65.88.
  • 65.88 + 32 = 97.88.

celsius_to_fahrenheit returns 97.88.

Edge cases to consider

  • 0 degrees Celsius
  • negative Celsius temperatures
  • a value that produces a non-integer Fahrenheit result
  • the freezing/boiling reference points (0 and 100)

Common mistakes

  • integer division truncating 9 / 5 to 1 before multiplying
  • getting the +32 offset in the wrong place
  • declaring the result as int and losing the fractional part

Optional extension challenge

Add the reverse conversion (Fahrenheit to Celsius) and a Celsius-to-Kelvin conversion as extra functions.

You can re-fetch the starter code for this question: prac_q9.c.

Question 10Digit Count

(2 marks)

Estimated time: 10-15 minutes

A form-validation routine needs to check that a numeric ID entered by a user has exactly the expected number of digits. Write a function that returns the number of decimal digits in a non-negative integer.

The number 0 has 1 digit (not 0 digits), since it is still written using a single digit character.

Your task is to complete the function int digit_count(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q10.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q10.c:

TERMINAL
gcc -Wall -Wextra -o prac_q10 prac_q10.c
./prac_q10
4325
4

./prac_q10
0
1

./prac_q10
7
1

Assumptions / Restrictions / Clarifications

  • n is non-negative.
  • digit_count(0) should return 1, not 0.
  • n may have any number of digits that fits within a normal int.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • loop-until-zero pattern
  • integer division by 10
  • handling a boundary value

Worked example

For example:

For n = 0:

  • the special case n == 0 is detected immediately.

digit_count returns 1, since "0" is written with a single digit character, even though the value itself is zero.

Edge cases to consider

  • the number 0, which has exactly 1 digit
  • a single-digit number
  • a large number near the int maximum

Common mistakes

  • returning 0 for the input 0 because the while loop body never runs
  • off-by-one in the count when the loop terminates

Optional extension challenge

Count digits in an arbitrary base b (2..16) passed as a parameter rather than always base 10.

You can re-fetch the starter code for this question: prac_q10.c.

Question 11Array Average

(2 marks)

Estimated time: 10-15 minutes

A teacher has recorded a class's quiz scores in an array of integers and wants to display the average score to two or three decimal places. Write a function that computes the average (mean) of an array of n integers, as a double.

Because the average of integers is often not itself a whole number, the function must return a double rather than truncating to an int.

Your task is to complete the function double array_average(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q11.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q11.c:

TERMINAL
gcc -Wall -Wextra -o prac_q11 prac_q11.c
./prac_q11
4
1 2 3 4
2.5

./prac_q11
1
7
7

./prac_q11
4
-2 4 -6 8
1

Assumptions / Restrictions / Clarifications

  • You may assume n is greater than 0.
  • arr may contain negative values, zero, and positive values in any combination.
  • The result should be computed using floating-point division, not integer division.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • summing an array
  • integer-to-double conversion
  • floating-point division

Worked example

For example:

For arr = [1, 2, 3, 4]:

  • the sum of the elements is 1 + 2 + 3 + 4 = 10.
  • dividing by n = 4 using floating-point division gives 2.5.

array_average returns 2.5.

Edge cases to consider

  • a single-element array
  • an array whose sum does not divide evenly
  • negative values pulling the average below zero

Common mistakes

  • integer division discarding the fractional part of the average
  • overflowing the running sum for very large arrays

Optional extension challenge

Return a trimmed average that ignores the single smallest and single largest values (when n is large enough).

You can re-fetch the starter code for this question: prac_q11.c.

Question 12Is Even Array

(2 marks)

Estimated time: 10-15 minutes

A quality-control check on a batch of measurements needs to confirm that every reading in an array is an even number before proceeding with further processing. Write a function that returns 1 if every element of an array of n integers is even, and 0 otherwise.

Your function should be able to stop early as soon as it finds an odd element, since at that point the answer is already known.

Your task is to complete the function int all_even(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q12.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q12.c:

TERMINAL
gcc -Wall -Wextra -o prac_q12 prac_q12.c
./prac_q12
4
2 4 6 8
1

./prac_q12
3
2 3 6
0

./prac_q12
0
1

Assumptions / Restrictions / Clarifications

  • If n is 0, the function should return 1 (vacuously true, since there is no counter-example).
  • arr may contain negative numbers; negative even numbers such as -4 still count as even.
  • arr may contain zero, which is considered even.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • array traversal
  • modulo to test parity
  • all-must-hold (universal) checks with early exit

Worked example

For example:

For arr = [2, 3, 6]:

  • 2 % 2 == 0, so it is even, keep scanning.
  • 3 % 2 != 0, so an odd element has been found.

all_even returns 0 immediately, without checking the remaining element 6.

Edge cases to consider

  • empty array (vacuously true, returns 1)
  • an array containing 0 (which is even)
  • an array with a single odd value that must flip the result to 0
  • negative even and odd values

Common mistakes

  • returning 0/1 based only on the last element instead of all elements
  • assuming negatives are never even (e.g. -4 is even)
  • returning early with 1 before checking the whole array

Optional extension challenge

Return the index of the first odd element (or -1 if all are even) instead of a plain boolean.

You can re-fetch the starter code for this question: prac_q12.c.

Question 13Reverse an Array In Place

(2 marks)

Estimated time: 10-15 minutes

A playlist shuffler feature needs to be able to play a list of track indices in reverse order without using any extra memory for a second list. Write a function that reverses the order of elements in an array of n integers in place (i.e. modifying the original array, using no extra array).

After the function returns, the caller's array should contain the same values but in reverse order; no new array should be allocated or returned.

Your task is to complete the function void reverse_array(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q13.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q13.c:

TERMINAL
gcc -Wall -Wextra -o prac_q13 prac_q13.c
./prac_q13
5
1 2 3 4 5
5 4 3 2 1

./prac_q13
4
10 20 30 40
40 30 20 10

./prac_q13
0

Assumptions / Restrictions / Clarifications

  • n may be 0, in which case the function should do nothing.
  • You must not allocate a second array to perform the reversal.
  • arr may contain duplicate values.
  • The array should be reversed using swaps working inward from both ends.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • two-pointer / two-index technique
  • swapping elements
  • in-place modification without extra arrays

Worked example

For example:

For arr = [1, 2, 3, 4, 5]:

  • i = 0, j = 4: swap arr[0] and arr[4] -> [5, 2, 3, 4, 1].
  • i = 1, j = 3: swap arr[1] and arr[3] -> [5, 4, 3, 2, 1].
  • i = 2, j = 2: i is no longer less than j, so the loop stops.

The array printed by main is "5 4 3 2 1".

Edge cases to consider

  • empty array (nothing to do)
  • a single-element array (already reversed)
  • an even vs odd length (middle element must stay put)

Common mistakes

  • looping over the whole array and swapping twice, undoing the reversal
  • off-by-one when computing the mirrored index (n - 1 - i)
  • allocating a second array when the task says in place

Optional extension challenge

Reverse only a sub-range [lo, hi] of the array given two extra index parameters.

You can re-fetch the starter code for this question: prac_q13.c.

Question 14Find Minimum in Array

(2 marks)

Estimated time: 10-15 minutes

A weather logging system stores a day's temperature readings in an array and needs to quickly identify the coldest reading of the day. Write a function that returns the smallest value in an array of n integers.

The array is not guaranteed to be sorted, so your function must examine every element to be sure it has found the true minimum.

Your task is to complete the function int find_min(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q14.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q14.c:

TERMINAL
gcc -Wall -Wextra -o prac_q14 prac_q14.c
./prac_q14
5
5 3 8 1 9
1

./prac_q14
3
-4 -10 -2
-10

./prac_q14
1
42
42

Assumptions / Restrictions / Clarifications

  • You may assume n is greater than 0; the function is never called on an empty array.
  • arr may contain negative numbers, zero, positive numbers, or duplicate values.
  • You should not modify the contents of arr.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • tracking a running best value
  • initialising from the first element
  • linear scan

Worked example

For example:

For arr = [-4, -10, -2]:

  • min starts as arr[0] = -4.
  • -10 < -4, so min becomes -10.
  • -2 is not less than -10, min stays -10.

find_min returns -10.

Edge cases to consider

  • a single-element array
  • all elements equal
  • the minimum being the first or the last element
  • arrays containing negative values

Common mistakes

  • initialising the minimum to 0 (wrong when all values are positive or all negative)
  • starting the scan before there is a valid first element to compare
  • returning the index instead of the value (or vice versa)

Optional extension challenge

Return both the minimum and maximum in a single pass using two output pointer parameters.

You can re-fetch the starter code for this question: prac_q14.c.

Question 15Factorial (Iterative)

(2 marks)

Estimated time: 10-15 minutes

A combinatorics teaching tool needs to compute factorials to demonstrate permutation counts to students. Write a function that computes n! for a non-negative integer n using iteration (a loop), returning the result as a long long.

Recall that n! is the product of all positive integers from 1 up to n, and that 0! is defined to be 1.

Your task is to complete the function long long factorial(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q15.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q15.c:

TERMINAL
gcc -Wall -Wextra -o prac_q15 prac_q15.c
./prac_q15
5
120

./prac_q15
0
1

./prac_q15
1
1

./prac_q15
20
2432902008176640000

Assumptions / Restrictions / Clarifications

  • You may assume 0 <= n <= 20 (so the result always fits in a long long).
  • factorial(0) should return 1, by definition.
  • You must use iteration (a loop); a recursive solution is not required for this problem.
  • The return type is long long, not int, to avoid overflow on both native and browser compilers.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • accumulating a product in a loop
  • the empty-product base case
  • integer overflow awareness

Worked example

For example:

For n = 5:

  • result starts at 1.
  • multiplying by 2, 3, 4, then 5 in turn: 1 * 2 = 2, 2 * 3 = 6, 6 * 4 = 24, 24 * 5 = 120.

factorial returns 120.

Edge cases to consider

  • 0! which must equal 1
  • 1! which must equal 1
  • a value large enough to overflow a 32-bit int (e.g. 13!)

Common mistakes

  • initialising the product to 0 so every result is 0
  • starting the loop at 0 and multiplying by 0
  • ignoring overflow for inputs above 12

Optional extension challenge

Rewrite it recursively, then compare which version overflows first as the input grows.

You can re-fetch the starter code for this question: prac_q15.c.

Question 16Is Prime

(2 marks)

Estimated time: 10-15 minutes

A cryptography teaching tool needs a basic building block to test whether small integers are prime numbers before using them in further calculations. Write a function that determines whether a given integer greater than 1 is prime, returning 1 if it is prime and 0 otherwise.

Recall that a prime number is a number greater than 1 whose only positive divisors are 1 and itself.

Your task is to complete the function int is_prime(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q16.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q16.c:

TERMINAL
gcc -Wall -Wextra -o prac_q16 prac_q16.c -lm
./prac_q16
7
1

./prac_q16
8
0

./prac_q16
2
1

./prac_q16
9973
1

Assumptions / Restrictions / Clarifications

  • You may assume n > 1; the function is never called with n <= 1.
  • An efficient solution only needs to check divisors up to sqrt(n), rather than all the way up to n.
  • 2 is the smallest prime and should be correctly identified as prime.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • loops and modulo
  • early exit on finding a factor
  • handling small boundary values

Worked example

For example:

For n = 8:

  • i = 2: 2 * 2 = 4 <= 8, and 8 % 2 == 0, so a divisor has been found.

is_prime returns 0 immediately, since 8 = 2 * 4 is not prime.

Edge cases to consider

  • 2, the only even prime
  • 3, the smallest odd prime
  • a large prime that forces the full loop to run
  • a square of a prime (e.g. 9 or 25)

Common mistakes

  • checking divisors up to n instead of stopping at sqrt(n)
  • starting the divisor loop at 1, which always divides
  • not returning 0 as soon as a factor is found

Optional extension challenge

Print all prime factors of n instead of just a boolean yes/no.

You can re-fetch the starter code for this question: prac_q16.c.

Question 17Count Vowels

(2 marks)

Estimated time: 10-15 minutes

A simple spell-checking tool needs to estimate how vowel-heavy a piece of text is as a rough heuristic. Write a function that counts the number of vowels ('a', 'e', 'i', 'o', 'u', case insensitive) in a null-terminated string.

The count should include both lowercase and uppercase vowels, and should ignore all other characters, including consonants, digits, spaces and punctuation.

Your task is to complete the function int count_vowels(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q17.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q17.c:

TERMINAL
gcc -Wall -Wextra -o prac_q17 prac_q17.c
./prac_q17
Hello World
3

./prac_q17
xyz
0

./prac_q17
AEIOU
5

./prac_q17

0

Assumptions / Restrictions / Clarifications

  • The string may be empty, in which case the function should return 0.
  • Only the characters 'a', 'e', 'i', 'o', 'u' and their uppercase forms count as vowels.
  • The letter 'y' is never counted as a vowel.
  • Non-alphabetic characters (digits, spaces, punctuation) must be ignored.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • string traversal
  • membership test against a small set
  • case handling

Worked example

For example:

For s = "Hello World":

  • 'e' and 'o' in "Hello" are vowels (2).
  • 'o' in "World" is a vowel (1 more).
  • every other character ('H','l','l','W','r','l','d', the space) is not a vowel.

count_vowels returns 3.

Edge cases to consider

  • the empty string
  • a string with no vowels
  • uppercase vowels that should also count
  • the letter 'y' (decide and document whether it counts)

Common mistakes

  • only checking lowercase vowels and missing uppercase ones
  • a long chain of == comparisons with a mistyped vowel
  • counting 'y' inconsistently

Optional extension challenge

Return counts of each individual vowel via five output parameters, not just the total.

You can re-fetch the starter code for this question: prac_q17.c.

Question 18Convert Cents to Coins

(2 marks)

Estimated time: 10-15 minutes

A vending machine's change-making logic needs to work out the fewest coins possible to return to a customer as change. Write a function that, given a non-negative number of cents, returns the minimum number of Australian coins ($2, $1, 50c, 20c, 10c, 5c) needed to make up that amount using a greedy approach (always use the largest coin that fits).

Because Australian coin denominations happen to form a 'canonical' coin system, the greedy approach (repeatedly subtracting the largest coin that still fits) is guaranteed to produce the true minimum number of coins.

Your task is to complete the function int min_coins(int cents), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q18.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q18.c:

TERMINAL
gcc -Wall -Wextra -o prac_q18 prac_q18.c
./prac_q18
85
4

./prac_q18
0
0

./prac_q18
500
3

Assumptions / Restrictions / Clarifications

  • cents is non-negative.
  • Coin values in cents are: 200, 100, 50, 20, 10, 5.
  • cents is guaranteed to be exactly representable using these coin denominations (i.e. it is a multiple of 5).
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • greedy denomination breakdown
  • integer division and modulo
  • greedy accumulation

Worked example

For example:

For cents = 85:

  • one 50c coin is used, leaving 35.
  • one 20c coin is used, leaving 15.
  • one 10c coin is used, leaving 5.
  • one 5c coin is used, leaving 0.

That is 4 coins in total, so min_coins returns 4.

Edge cases to consider

  • 0 cents, returning 0
  • an amount that uses only the smallest coin
  • an amount that is an exact multiple of the largest coin
  • an amount requiring at least one of every denomination

Common mistakes

  • processing denominations from smallest to largest, breaking the greedy result
  • forgetting to subtract (via %) after each denomination
  • off-by-one leaving leftover cents unaccounted for

Optional extension challenge

Report the individual coin counts as well, and handle an arbitrary list of denominations passed as an array.

You can re-fetch the starter code for this question: prac_q18.c.

Question 19Is Palindrome String

(2 marks)

Estimated time: 10-15 minutes

A word-puzzle game wants to highlight palindromic words that players enter, such as "level" or "racecar". Write a function that determines whether a given null-terminated string reads the same forwards and backwards, returning 1 if it is a palindrome and 0 otherwise.

Comparison is case-sensitive and includes all characters exactly as they appear in the string, so spaces and capitalisation matter.

Your task is to complete the function int is_palindrome(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q19.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q19.c:

TERMINAL
gcc -Wall -Wextra -o prac_q19 prac_q19.c
./prac_q19
level
1

./prac_q19
hello
0

./prac_q19

1

./prac_q19
Level
0

Assumptions / Restrictions / Clarifications

  • The empty string and single-character strings are palindromes.
  • Comparison is case-sensitive; no case-folding is performed.
  • Every character, including spaces and punctuation, is compared exactly as given.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • two-index inward scan
  • character equality
  • read-only string handling

Worked example

For example:

For s = "Level":

  • i = 0, j = 4: s[0] = 'L', s[4] = 'l' -- these differ because comparison is case-sensitive.

is_palindrome returns 0 immediately, even though "level" (all lowercase) would be a palindrome.

Edge cases to consider

  • the empty string (a palindrome)
  • a single character (a palindrome)
  • even vs odd length strings
  • case sensitivity and whether spaces matter

Common mistakes

  • comparing from both ends but overshooting past the middle
  • using strlen off by one when computing the far index
  • modifying the string while checking it

Optional extension challenge

Ignore spaces, punctuation and letter case so that 'A man a plan a canal Panama' is recognised as a palindrome.

You can re-fetch the starter code for this question: prac_q19.c.

Question 20Absolute Value Without abs

(2 marks)

Estimated time: 8-12 minutes

A signal-processing routine needs the magnitude of a reading regardless of its sign, but the coding standard for this project forbids calling the standard library function abs. Write a function that returns the absolute value of an integer using only basic arithmetic and a comparison.

Your task is to complete the function int my_abs(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q20.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q20.c:

TERMINAL
gcc -Wall -Wextra -o prac_q20 prac_q20.c
./prac_q20
-7
7

./prac_q20
0
0

./prac_q20
42
42

Assumptions / Restrictions / Clarifications

  • Do not call the library function abs.
  • n may be negative, zero or positive.
  • You may assume n is never INT_MIN (whose negation would overflow).
  • Do not print anything inside my_abs.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • relational comparison
  • unary negation
  • returning one of two branches

Worked example

For example:

For the input -7:

  • -7 is less than 0, so the function returns -(-7).
  • -(-7) is 7, so my_abs returns 7.

Edge cases to consider

  • 0, whose absolute value is 0
  • a value that is already positive
  • the most negative int (excluded here because negating it overflows)

Common mistakes

  • calling abs, which the spec forbids
  • returning n unchanged for negatives
  • using n * -1 but forgetting it still needs the sign test

Optional extension challenge

Write a my_labs for long and observe how the INT_MIN overflow problem behaves at the long boundary.

You can re-fetch the starter code for this question: prac_q20.c.

Question 21Clamp a Value to a Range

(2 marks)

Estimated time: 8-12 minutes

A volume control must never let the level go below a minimum or above a maximum, no matter what the user requests. Write a function that clamps a value into the inclusive range [lo, hi]: values below lo become lo, values above hi become hi, and values already in range are returned unchanged.

Your task is to complete the function int clamp(int value, int lo, int hi), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q21.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q21.c:

TERMINAL
gcc -Wall -Wextra -o prac_q21 prac_q21.c
./prac_q21
5 0 10
5

./prac_q21
-3 0 10
0

./prac_q21
15 0 10
10

Assumptions / Restrictions / Clarifications

  • You may assume lo <= hi.
  • The range is inclusive of both lo and hi.
  • Do not print anything inside clamp.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • chained if statements
  • range boundary logic
  • returning early

Worked example

For example:

For value = -3 with range [0, 10]:

  • -3 is less than lo (0), so the function returns lo.
  • clamp therefore returns 0.

Edge cases to consider

  • value exactly equal to lo or hi (returned unchanged)
  • value already inside the range
  • lo == hi, collapsing the range to a single allowed value

Common mistakes

  • using exclusive comparisons and clamping a valid boundary value
  • checking only one of the two bounds
  • returning lo/hi swapped

Optional extension challenge

Write a float version and a wrap version that, instead of clamping, wraps out-of-range values around the interval.

You can re-fetch the starter code for this question: prac_q21.c.

Question 22Is Leap Year

(2 marks)

Estimated time: 8-12 minutes

A calendar application must decide whether February has 28 or 29 days. Write a function that returns 1 if the given year is a leap year and 0 otherwise.

A year is a leap year if it is divisible by 4, except that years divisible by 100 are not leap years unless they are also divisible by 400.

Your task is to complete the function int is_leap_year(int year), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q22.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q22.c:

TERMINAL
gcc -Wall -Wextra -o prac_q22 prac_q22.c
./prac_q22
2000
1

./prac_q22
1900
0

./prac_q22
2024
1

./prac_q22
2023
0

Assumptions / Restrictions / Clarifications

  • You may assume year is a positive integer.
  • Return exactly 1 for a leap year and 0 otherwise.
  • Do not print anything inside is_leap_year.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • the modulo operator
  • combining divisibility rules
  • ordering conditions correctly

Worked example

For example:

For the year 1900:

  • 1900 is not divisible by 400.
  • 1900 is divisible by 100, so the century rule makes it NOT a leap year.
  • is_leap_year returns 0.

Edge cases to consider

  • century years like 1900 (not a leap year)
  • 400-divisible years like 2000 (a leap year)
  • an ordinary divisible-by-4 year like 2024
  • a non-leap year like 2023

Common mistakes

  • checking divisibility by 4 first and returning before applying the 100/400 rules
  • getting the 100 and 400 exceptions backwards
  • using && / || in one expression and mis-ordering the precedence

Optional extension challenge

Return the number of days in a given month of a given year, using the leap-year test for February.

You can re-fetch the starter code for this question: prac_q22.c.

Question 23Integer Power (Iterative)

(2 marks)

Estimated time: 8-12 minutes

A fixed-point maths helper needs to raise an integer base to a non-negative integer power without calling pow from the maths library. Write a function that computes base raised to the power exp using a loop.

Your task is to complete the function int int_pow(int base, int exp), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q23.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q23.c:

TERMINAL
gcc -Wall -Wextra -o prac_q23 prac_q23.c
./prac_q23
2 10
1024

./prac_q23
5 0
1

./prac_q23
3 4
81

Assumptions / Restrictions / Clarifications

  • You may assume exp >= 0.
  • Recall that any base raised to the power 0 is 1, including 0 to the power 0 which you should treat as 1.
  • Do not call pow or any maths-library function.
  • You may assume the result fits in an int.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • accumulating a product in a loop
  • the empty-product identity
  • loop counting

Worked example

For example:

For base 2 and exp 10:

  • result starts at 1 and is multiplied by 2 exactly ten times.
  • 1 * 2^10 = 1024, so int_pow returns 1024.

Edge cases to consider

  • exp == 0 (result is 1 for any base)
  • base == 0 with positive exp (result 0)
  • base == 1 (result always 1)
  • a large exp that risks overflow

Common mistakes

  • initialising result to 0 so every answer is 0
  • looping exp + 1 times (off-by-one)
  • returning base instead of the accumulated result when exp is 1

Optional extension challenge

Implement fast exponentiation by squaring, which uses O(log exp) multiplications instead of O(exp).

You can re-fetch the starter code for this question: prac_q23.c.

Question 24Count Even Digits

(2 marks)

Estimated time: 10-15 minutes

A number-styling tool wants to know how many of a number's decimal digits are even (0, 2, 4, 6 or 8). Write a function that returns the count of even digits in the given integer.

The sign of a negative number should be ignored; only its digits matter.

Your task is to complete the function int count_even_digits(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q24.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q24.c:

TERMINAL
gcc -Wall -Wextra -o prac_q24 prac_q24.c
./prac_q24
2468
4

./prac_q24
13579
0

./prac_q24
0
1

./prac_q24
-2040
4

Assumptions / Restrictions / Clarifications

  • A negative number has the same even-digit count as its absolute value.
  • The number 0 has a single digit, which is even.
  • You may assume n is not INT_MIN.
  • Do not print anything inside count_even_digits.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • digit extraction with % 10 and / 10
  • parity testing
  • handling the zero case

Worked example

For example:

For the input -2040:

  • the sign is ignored, leaving the digits 2, 0, 4, 0.
  • all four digits (2, 0, 4, 0) are even.
  • count_even_digits returns 4.

Edge cases to consider

  • 0, which is a single even digit and must return 1
  • a negative number (sign ignored)
  • a number with no even digits at all
  • trailing zeros, which are even digits

Common mistakes

  • returning 0 for the input 0 because the loop never runs
  • counting the minus sign as a digit
  • treating 0 as odd

Optional extension challenge

Return the count of even digits minus odd digits, so the sign of the result reveals which kind dominates.

You can re-fetch the starter code for this question: prac_q24.c.

Question 25GCD (Recursive)

(2 marks)

Estimated time: 10-15 minutes

A fraction-simplifying routine needs the greatest common divisor of two numbers so it can reduce fractions to lowest terms. Write a RECURSIVE function that computes the greatest common divisor of two non-negative integers using Euclid's algorithm.

Euclid's algorithm relies on the fact that gcd(a, b) equals gcd(b, a % b), and that gcd(a, 0) equals a.

Your task is to complete the function int gcd(int a, int b), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q25.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q25.c:

TERMINAL
gcc -Wall -Wextra -o prac_q25 prac_q25.c
./prac_q25
48 36
12

./prac_q25
17 5
1

./prac_q25
100 0
100

./prac_q25
13 13
13

Assumptions / Restrictions / Clarifications

  • You may assume a and b are non-negative and not both zero.
  • Your solution must be recursive (it must call gcd).
  • gcd(a, 0) is a.
  • Do not print anything inside gcd.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • recursion with a base case
  • the modulo operator
  • Euclid's algorithm

Worked example

For example:

For a = 48, b = 36:

  • gcd(48, 36) calls gcd(36, 48 % 36) = gcd(36, 12).
  • gcd(36, 12) calls gcd(12, 36 % 12) = gcd(12, 0).
  • gcd(12, 0) hits the base case and returns 12.

Edge cases to consider

  • b == 0 (base case, returns a)
  • a smaller than b (the first step swaps them)
  • the two numbers being equal
  • one number being a multiple of the other

Common mistakes

  • writing an iterative loop when a recursive solution is required
  • forgetting the b == 0 base case, causing infinite recursion or a divide-by-zero
  • recursing with the arguments in the wrong order

Optional extension challenge

Extend it to also return the least common multiple, computed as a / gcd(a, b) * b to avoid overflow.

You can re-fetch the starter code for this question: prac_q25.c.

Question 26Sum From 1 to N (Recursive)

(2 marks)

Estimated time: 8-12 minutes

As an exercise in recursion, write a function that returns the sum of all integers from 1 up to and including n, but you must compute it RECURSIVELY rather than with a loop or a closed-form formula.

Your task is to complete the function int sum_to_n(int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q26.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q26.c:

TERMINAL
gcc -Wall -Wextra -o prac_q26 prac_q26.c
./prac_q26
5
15

./prac_q26
0
0

./prac_q26
100
5050

Assumptions / Restrictions / Clarifications

  • You may assume n >= 0.
  • sum_to_n(0) is 0 (the empty sum).
  • Your solution must be recursive and must not use the n * (n + 1) / 2 formula.
  • Do not print anything inside sum_to_n.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • recursion
  • identifying a base case
  • building a result while unwinding

Worked example

For example:

For n = 5:

  • sum_to_n(5) = 5 + sum_to_n(4) = 5 + 4 + sum_to_n(3) ... down to sum_to_n(0).
  • sum_to_n(0) returns 0, unwinding to 5 + 4 + 3 + 2 + 1 + 0.
  • the total is 15, so sum_to_n returns 15.

Edge cases to consider

  • n == 0, the base case returning 0
  • n == 1 (a single-step recursion)
  • a larger n that produces deep recursion

Common mistakes

  • missing the base case, causing infinite recursion
  • using the closed-form formula when recursion is required
  • recursing on n instead of n - 1 and never reaching the base case

Optional extension challenge

Rewrite it to sum only the even numbers from 1 to n recursively, without changing the return type.

You can re-fetch the starter code for this question: prac_q26.c.

Question 27Max Consecutive Equal Elements

(2 marks)

Estimated time: 10-15 minutes

A monitoring dashboard wants to report the longest streak of identical readings in a log. Write a function that returns the length of the longest run of equal values that appear consecutively in an array of n integers.

Your task is to complete the function int max_run(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q27.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q27.c:

TERMINAL
gcc -Wall -Wextra -o prac_q27 prac_q27.c
./prac_q27
6
1 1 2 2 2 3
3

./prac_q27
3
7 8 9
1

./prac_q27
0
0

Assumptions / Restrictions / Clarifications

  • If n is 0 the function returns 0.
  • A run is a maximal sequence of equal values in adjacent positions.
  • A single element counts as a run of length 1.
  • Do not print anything inside max_run.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • comparing adjacent array elements
  • tracking a running and a best value
  • resetting a counter

Worked example

For example:

For the array 1 1 2 2 2 3:

  • the run of 1s has length 2.
  • the run of 2s has length 3.
  • the run of 3s has length 1.

The longest run is 3, so max_run returns 3.

Edge cases to consider

  • empty array returning 0
  • an array with no repeats (answer 1)
  • an array where every element is equal (answer n)
  • the longest run at the very end

Common mistakes

  • comparing arr[i] to arr[0] instead of the previous element
  • starting the run counters at 0, giving an answer one too small
  • starting the loop at i = 0 and reading arr[-1]

Optional extension challenge

Also return the value that forms the longest run via an output pointer, breaking ties by earliest run.

You can re-fetch the starter code for this question: prac_q27.c.

Question 28Count Nodes In A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q28.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A playlist is stored as a linked list, one node per track, and the player needs to show how many tracks are queued. Write a function that walks a linked list and returns the number of nodes in it.

The list may be empty, in which case there are no nodes to count.

Your task is to complete the function int list_length(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q28.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q28.c:

TERMINAL
gcc -Wall -Wextra -o prac_q28 prac_q28.c
./prac_q28
5
4 8 15 16 23
5

./prac_q28
1
7
1

./prac_q28
0
0

Assumptions / Restrictions / Clarifications

  • An empty list is represented by head being NULL, and the answer is then 0.
  • Do not modify the list. Your function must not change any next or value field.
  • Do not call malloc or free inside list_length.
  • Do not print anything inside list_length; only main should print.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • walking a linked list with a pointer
  • using NULL as the end-of-list marker
  • accumulating a count

Edge cases to consider

  • an empty list, where head is NULL and the loop body never runs
  • a list with exactly one node
  • a long list, to make sure the loop actually advances

Common mistakes

  • writing current = current->next inside an if instead of the loop body, so the loop never ends
  • starting the count at 1, which over-counts by one
  • testing current->next != NULL instead of current != NULL, which misses the last node

Optional extension challenge

Write a second function that returns the number of nodes without using a loop, by calling itself on head->next.

You can re-fetch the starter code for this question: prac_q28.c.

Question 29Largest Value In A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q29.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A weather station appends each hour's temperature reading to a linked list. Write a function that returns the largest value stored in the list.

You may assume the list contains at least one node, so there is always an answer to return.

Your task is to complete the function int list_max(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q29.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q29.c:

TERMINAL
gcc -Wall -Wextra -o prac_q29 prac_q29.c
./prac_q29
5
3 9 2 9 1
9

./prac_q29
4
-8 -2 -40 -5
-2

./prac_q29
1
6
6

Assumptions / Restrictions / Clarifications

  • The list is guaranteed to contain at least one node.
  • Values may be negative, so do not assume the largest value is positive.
  • Do not modify the list.
  • Do not call malloc or free inside list_max.
  • Do not print anything inside list_max.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • tracking a running maximum
  • seeding a comparison from the first element
  • traversing from the second node onward

Edge cases to consider

  • a list where every value is negative, so a 0 starting point would be wrong
  • a single-node list, where the answer is that node's value
  • the largest value appearing more than once
  • the largest value being the very first or very last node

Common mistakes

  • starting max at 0, which breaks on an all-negative list
  • starting the walk at head instead of head->next after seeding from head (harmless, but a sign the seed was forgotten)
  • comparing current->next->value and reading past the end of the list

Optional extension challenge

Return the smallest value as well, through a second int pointer parameter, in a single pass.

You can re-fetch the starter code for this question: prac_q29.c.

Question 30Search A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q30.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A door system keeps the ID of every card that is allowed through in a linked list. Write a function that reports whether a given value appears anywhere in the list.

Return 1 if the value is found and 0 if it is not.

Your task is to complete the function int list_contains(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q30.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q30.c:

TERMINAL
gcc -Wall -Wextra -o prac_q30 prac_q30.c
./prac_q30
5
2 4 6 8 10
6
1

./prac_q30
4
1 2 3 4
9
0

./prac_q30
0
5
0

Assumptions / Restrictions / Clarifications

  • An empty list contains nothing, so the answer for an empty list is always 0.
  • Return as soon as the value is found; there is no need to keep walking.
  • Do not modify the list.
  • Do not call malloc or free inside list_contains.
  • Do not print anything inside list_contains.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • searching a linked structure
  • returning early from inside a loop
  • handling the not-found case after the loop

Edge cases to consider

  • an empty list, which can never contain the value
  • the value sitting in the very first node
  • the value sitting in the very last node
  • the value appearing several times, which should still return 1

Common mistakes

  • returning 0 from inside the loop on the first mismatch, which only ever checks the first node
  • forgetting the return after the loop, so the function falls off the end
  • using = instead of == in the comparison

Optional extension challenge

Return the position of the first match instead, counting from 0, and -1 when the value is absent.

You can re-fetch the starter code for this question: prac_q30.c.

Question 31Count Occurrences In A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q31.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A vending machine logs every coin it accepts into a linked list. Write a function that counts how many nodes hold a particular value.

If the value never appears, the count is 0.

Your task is to complete the function int count_value(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q31.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q31.c:

TERMINAL
gcc -Wall -Wextra -o prac_q31 prac_q31.c
./prac_q31
6
1 2 1 3 1 4
1
3

./prac_q31
4
5 6 7 8
9
0

./prac_q31
0
3
0

Assumptions / Restrictions / Clarifications

  • An empty list gives a count of 0.
  • Every matching node counts, not just the first one.
  • Do not modify the list.
  • Do not call malloc or free inside count_value.
  • Do not print anything inside count_value.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • counting matches while traversing
  • combining a condition with an accumulator
  • walking to the end rather than stopping early

Edge cases to consider

  • an empty list
  • no node matching at all
  • every node matching

Common mistakes

  • returning as soon as one match is found, which always gives 1
  • resetting the counter inside the loop
  • advancing the pointer only inside the if, so a non-match loops forever

Optional extension challenge

Count how many nodes hold a value strictly between two bounds passed as extra parameters.

You can re-fetch the starter code for this question: prac_q31.c.

Question 32Count Even Values In A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q32.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A turnstile records the number of people in each group entering a venue, one node per group. Write a function that counts how many nodes hold an even number.

Zero is even and must be counted.

Your task is to complete the function int count_even(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q32.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q32.c:

TERMINAL
gcc -Wall -Wextra -o prac_q32 prac_q32.c
./prac_q32
6
1 2 3 4 5 6
3

./prac_q32
4
-4 -3 0 7
2

./prac_q32
0
0

Assumptions / Restrictions / Clarifications

  • Zero counts as even.
  • Negative even numbers count too, so -4 is even.
  • Do not modify the list.
  • Do not call malloc or free inside count_even.
  • Do not print anything inside count_even.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • the remainder operator on list data
  • counting under a condition
  • reasoning about negative operands

Edge cases to consider

  • a list containing 0, which is even
  • negative even values such as -4
  • an empty list

Common mistakes

  • writing value % 2 == 1 to test for odd, which is false for negative odd numbers in C
  • counting odd values by mistake
  • forgetting that 0 is even

Optional extension challenge

Return the count of odd values as well, through an int pointer parameter.

You can re-fetch the starter code for this question: prac_q32.c.

Question 33Sum A Linked List From A Position

(2 marks)

Estimated time: 15-20 minutes

Note prac_q33.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A bank statement is stored newest-first as a linked list. Write a function that adds up every value from a given position to the end of the list, counting positions from 0.

If the position is past the end of the list there is nothing to add, and the total is 0.

Your task is to complete the function int sum_from(struct node *head, int position), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q33.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q33.c:

TERMINAL
gcc -Wall -Wextra -o prac_q33 prac_q33.c
./prac_q33
5
1 2 3 4 5
2
12

./prac_q33
4
10 20 30 40
0
100

./prac_q33
3
1 2 3
9
0

Assumptions / Restrictions / Clarifications

  • Positions are counted from 0, so position 0 means the whole list.
  • A position past the end of the list gives a total of 0.
  • You may assume position is not negative.
  • Do not modify the list.
  • Do not print anything inside sum_from.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • skipping a fixed number of nodes before working
  • guarding a walk against running off the end
  • two loops over one traversal

Edge cases to consider

  • a position of 0, which sums the entire list
  • a position past the end, which sums nothing
  • a position landing exactly on the last node

Common mistakes

  • dropping the current != NULL test in the skip loop and dereferencing NULL
  • starting the index at 1, which is off by one
  • summing while skipping, which double-counts the front of the list

Optional extension challenge

Take an end position too, and sum only the nodes between the two positions.

You can re-fetch the starter code for this question: prac_q33.c.

Question 34Value At A Position

(2 marks)

Estimated time: 10-15 minutes

Note prac_q34.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A queue of print jobs is held in a linked list. Write a function that returns the value stored at a given position, counting from 0.

If the position does not exist in the list, return -1.

Your task is to complete the function int value_at(struct node *head, int position), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q34.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q34.c:

TERMINAL
gcc -Wall -Wextra -o prac_q34 prac_q34.c
./prac_q34
5
7 8 9 10 11
2
9

./prac_q34
3
4 5 6
0
4

./prac_q34
3
4 5 6
7
-1

Assumptions / Restrictions / Clarifications

  • Positions count from 0, so position 0 is the first node.
  • Return -1 if the position is past the end of the list.
  • You may assume position is not negative.
  • Do not modify the list.
  • Do not print anything inside value_at.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • keeping an index alongside a pointer
  • returning from inside a traversal
  • signalling not-found with a sentinel

Edge cases to consider

  • position 0, the first node
  • the last valid position
  • a position past the end, which returns -1
  • an empty list, which always returns -1

Common mistakes

  • counting positions from 1 instead of 0
  • checking index == position after advancing, which returns the wrong node
  • forgetting the -1 return, so the function falls off the end

Optional extension challenge

Return the value at a position counted from the END of the list instead.

You can re-fetch the starter code for this question: prac_q34.c.

Question 35Add To The Front Of A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q35.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A browser keeps its history newest-first in a linked list. Write a function that adds a new node holding a given value to the front of the list and returns the new head.

Adding to the front works even when the list is empty.

Your task is to complete the function struct node *push_front(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q35.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q35.c:

TERMINAL
gcc -Wall -Wextra -o prac_q35 prac_q35.c
./prac_q35
4
2 3 4 5
1
1 2 3 4 5

./prac_q35
0
9
9

./prac_q35
1
5
7
7 5

Assumptions / Restrictions / Clarifications

  • Allocate the new node with malloc.
  • Return the new head of the list, which is the node you just created.
  • Adding to an empty list gives a list of one node.
  • Do not print anything inside push_front.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • allocating a node with malloc
  • pointing a new node at an existing list
  • returning a new head

Edge cases to consider

  • an empty list, where the new node becomes the whole list
  • a list of one node
  • checking the old head is still reachable through the new node

Common mistakes

  • setting new_node->next to NULL, which throws the rest of the list away
  • forgetting to return the new node, so the caller keeps the old head
  • assigning to head inside the function and expecting the caller to see it

Optional extension challenge

Write the matching pop_front, which removes and frees the first node and returns the new head.

You can re-fetch the starter code for this question: prac_q35.c.

Question 36Add To The End Of A Linked List

(2 marks)

Estimated time: 15-20 minutes

Note prac_q36.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A ticket queue is stored as a linked list with the next person to serve at the front. Write a function that adds a node holding a given value to the END of the list and returns the head.

If the list is empty the new node becomes the head.

Your task is to complete the function struct node *push_back(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q36.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q36.c:

TERMINAL
gcc -Wall -Wextra -o prac_q36 prac_q36.c
./prac_q36
3
1 2 3
4
1 2 3 4

./prac_q36
0
7
7

./prac_q36
1
5
6
5 6

Assumptions / Restrictions / Clarifications

  • Allocate the new node with malloc and set its next to NULL.
  • If the list is empty, return the new node as the head.
  • Otherwise return the original head unchanged.
  • Do not print anything inside push_back.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • walking to the last node
  • the difference between current and current->next as a loop test
  • treating the empty list as a special case

Edge cases to consider

  • an empty list, which needs its own branch
  • a list of one node
  • confirming the new node's next is NULL so the list still ends

Common mistakes

  • looping while current != NULL, which walks past the last node and then dereferences NULL
  • forgetting the empty-list case and dereferencing a NULL head
  • returning the new node instead of the original head on a non-empty list

Optional extension challenge

Keep a tail pointer so appending does not need to walk the list every time.

You can re-fetch the starter code for this question: prac_q36.c.

Question 37Delete The First Node

(2 marks)

Estimated time: 10-15 minutes

Note prac_q37.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A task list is stored as a linked list and the front task has just been completed. Write a function that removes the first node, frees it, and returns the new head.

Deleting from an empty list leaves it empty.

Your task is to complete the function struct node *delete_first(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q37.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q37.c:

TERMINAL
gcc -Wall -Wextra -o prac_q37 prac_q37.c
./prac_q37
5
1 2 3 4 5
2 3 4 5

./prac_q37
1
9


./prac_q37
0

Assumptions / Restrictions / Clarifications

  • Call free on the node you remove.
  • Return the new head, which is the second node of the original list.
  • If the list is empty, return NULL.
  • If the list had one node, the result is an empty list, so return NULL.
  • Do not print anything inside delete_first.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • freeing a node without losing the rest of the list
  • saving a pointer before destroying what holds it
  • returning an updated head

Edge cases to consider

  • an empty list, which must not be dereferenced
  • a one-node list, which becomes empty
  • confirming the freed node is not read afterwards

Common mistakes

  • calling free(head) before reading head->next, which reads freed memory
  • forgetting to free at all, which leaks the node
  • returning head instead of the new head, leaving a dangling pointer

Optional extension challenge

Delete the first node only when it holds a given value, otherwise leave the list alone.

You can re-fetch the starter code for this question: prac_q37.c.

Question 38Count Values Above A Threshold

(2 marks)

Estimated time: 10-15 minutes

Note prac_q38.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A monitoring system stores each temperature reading in a linked list. Write a function that counts how many nodes hold a value strictly greater than a given limit.

A value exactly equal to the limit does not count.

Your task is to complete the function int count_above(struct node *head, int limit), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q38.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q38.c:

TERMINAL
gcc -Wall -Wextra -o prac_q38 prac_q38.c
./prac_q38
6
1 5 9 5 12 3
5
2

./prac_q38
3
1 2 3
10
0

./prac_q38
0
0
0

Assumptions / Restrictions / Clarifications

  • Only values strictly greater than limit count; equal values do not.
  • An empty list gives 0.
  • Do not modify the list.
  • Do not print anything inside count_above.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • applying a comparison to every node
  • the difference between > and >=
  • counting under a condition

Edge cases to consider

  • values exactly equal to the limit, which must not count
  • no value above the limit at all
  • a negative limit

Common mistakes

  • using >= and counting the boundary value
  • comparing against the wrong operand order
  • returning early on the first value above the limit

Optional extension challenge

Count values below a second limit too, so the function reports how many fall outside a range.

You can re-fetch the starter code for this question: prac_q38.c.

Question 39Sum Of Negative Values In A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q39.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

An account keeps every transaction in a linked list, with withdrawals stored as negative numbers. Write a function that adds up only the negative values and returns that total.

The total will be zero or negative. If there are no withdrawals, return 0.

Your task is to complete the function int sum_negative(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q39.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q39.c:

TERMINAL
gcc -Wall -Wextra -o prac_q39 prac_q39.c
./prac_q39
6
10 -5 3 -20 0 -1
-26

./prac_q39
3
1 2 3
0

./prac_q39
0
0

Assumptions / Restrictions / Clarifications

  • Zero is not negative and must not be included.
  • If no value is negative, return 0.
  • Do not modify the list.
  • Do not print anything inside sum_negative.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • filtering while accumulating
  • signed arithmetic on a running total
  • distinguishing zero from negative

Edge cases to consider

  • a list with no negative values, giving 0
  • a list containing 0, which must not be added
  • an empty list

Common mistakes

  • using <= 0 and folding zeroes in, which does not change the total but shows the wrong intent
  • adding the absolute value and returning a positive number
  • starting the total at the first value instead of 0

Optional extension challenge

Return the count of withdrawals as well, through an int pointer parameter.

You can re-fetch the starter code for this question: prac_q39.c.

Question 40Last Value In A Linked List

(2 marks)

Estimated time: 10-15 minutes

Note prac_q40.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A sensor appends each new reading to the end of a linked list, so the most recent reading is the last node. Write a function that returns the value in the last node.

You may assume the list has at least one node.

Your task is to complete the function int last_value(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q40.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q40.c:

TERMINAL
gcc -Wall -Wextra -o prac_q40 prac_q40.c
./prac_q40
4
3 6 9 12
12

./prac_q40
1
42
42

./prac_q40
2
-7 -8
-8

Assumptions / Restrictions / Clarifications

  • The list is guaranteed to have at least one node.
  • A one-node list means the first node is also the last.
  • Do not modify the list.
  • Do not print anything inside last_value.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • stopping ON the last node rather than past it
  • current->next as the loop condition
  • when a single node is both first and last

Edge cases to consider

  • a one-node list, where the loop never runs
  • a two-node list, the smallest case where the loop runs once
  • confirming the pointer is not NULL when the value is read

Common mistakes

  • looping while current != NULL, which ends with current as NULL and then crashes
  • returning head->value, which is the first node
  • keeping a previous pointer current that is never needed here

Optional extension challenge

Return the last value without a loop, by having the function call itself on head->next.

You can re-fetch the starter code for this question: prac_q40.c.

Question 41Count Words in a Sentence

(3 marks)

Estimated time: 12-18 minutes

A basic writing-assistant tool wants to give users a live word count as they type a sentence. Write a function that counts the number of words in a null-terminated string, where words are separated by one or more single space characters.

Leading and trailing spaces should not create extra (empty) words, and runs of multiple consecutive spaces between words should still only count as a single separator.

Your task is to complete the function int count_words(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q41.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q41.c:

TERMINAL
gcc -Wall -Wextra -o prac_q41 prac_q41.c
./prac_q41
the quick brown fox
4

./prac_q41
  hello   world 
2

./prac_q41

0

./prac_q41
     
0

Assumptions / Restrictions / Clarifications

  • Words are only separated by the space character ' ', not tabs or other whitespace.
  • An empty string, or a string containing only spaces, has 0 words.
  • Multiple consecutive spaces between words do not create extra empty words.
  • Punctuation attached to a word (e.g. "fox.") is still counted as part of that word.
  • Do not call scanf, getchar or fgets inside count_words; the string is passed in as an argument.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • scanning a string for word boundaries
  • tracking whether you are 'inside' a word
  • handling runs of separators

Worked example

For example:

For the string " hello world ":

  • the leading spaces are skipped (no word starts there).
  • 'hello' is the first word.
  • the run of 3 spaces between the words is treated as a single separator.
  • 'world' is the second word.
  • the trailing space does not start a new word.

There are 2 words in total, so count_words returns 2.

Edge cases to consider

  • the empty string (0 words)
  • a string that is all spaces (still 0 words)
  • leading and trailing spaces that must not create empty words
  • multiple consecutive spaces between two words

Common mistakes

  • counting spaces instead of words, giving one too many/few
  • double-counting a word when several spaces separate words
  • off-by-one from not handling the transition from space to non-space cleanly

Optional extension challenge

Extend it to also treat tab and newline characters as word separators, not just the space character.

You can re-fetch the starter code for this question: prac_q41.c.

Question 42Struct Rectangle Area and Perimeter

(3 marks)

Estimated time: 12-18 minutes

Note prac_q42.c uses the following data type:

C
struct rectangle {
    int width;
    int height;
};

A simple floor-planning tool represents each room as a rectangle and needs to report both its floor area (for cost estimation) and its perimeter (for skirting board length). A struct rectangle (see the provided starter header) stores an integer width and height.

Write two functions: one that computes the area of a rectangle, and one that computes its perimeter.

Your task is to complete the functions int rect_area(struct rectangle r) and int rect_perimeter(struct rectangle r), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q42.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q42.c:

TERMINAL
gcc -Wall -Wextra -o prac_q42 prac_q42.c
./prac_q42
4 5
20 18

./prac_q42
3 3
9 12

./prac_q42
1 10
10 22

Assumptions / Restrictions / Clarifications

  • width and height are always positive.
  • The rectangle is passed by value, so the functions should not need to modify it.
  • Area is width * height; perimeter is 2 * (width + height).
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • accessing struct fields with the . operator
  • passing a struct by value
  • computing geometric properties from record fields

Worked example

For example:

For width = 4 and height = 5:

  • area = width * height = 4 * 5 = 20.
  • perimeter = 2 * (width + height) = 2 * 9 = 18.

main prints "20 18".

Edge cases to consider

  • a square (width == height)
  • a rectangle where width and height differ significantly
  • large dimensions where area may exceed int range

Common mistakes

  • confusing the area and perimeter formulas
  • using -> on a struct passed by value instead of .
  • swapping width and height in calculations

Optional extension challenge

Add a function that returns 1 if one rectangle fits entirely inside another (ignoring rotation).

You can re-fetch the starter code for this question: prac_q42.c.

Question 43Struct Distance Between Two Points

(3 marks)

Estimated time: 12-18 minutes

Note prac_q43.c uses the following data type:

C
struct point {
    int x;
    int y;
};

A simple 2D geometry library needs a way to measure how far apart two points are, for example to compute how far a game character has moved. A struct point represents a location in 2D space with integer x and y coordinates (see the provided starter header). Write a function that computes the Euclidean separation between two points as a double.

Both points are passed by value (as whole structs), so your function should not need to use pointers to access their fields.

Your task is to complete the function double distance(struct point p1, struct point p2), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q43.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q43.c:

TERMINAL
gcc -Wall -Wextra -o prac_q43 prac_q43.c -lm
./prac_q43
0 0 3 4
5

./prac_q43
-2 -2 2 1
5

./prac_q43
7 7 7 7
0

Assumptions / Restrictions / Clarifications

  • The formula is sqrt((x2 - x1)^2 + (y2 - y1)^2).
  • You must #include <math.h> to use sqrt, and link with -lm if compiling manually.
  • x and y coordinates may be negative, zero, or positive.
  • p1 and p2 may refer to the same coordinates, in which case the result is 0.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • struct field access
  • the Euclidean-distance formula
  • using sqrt from <math.h>

Worked example

For example:

For p1 = (0, 0) and p2 = (3, 4):

  • dx = 3 - 0 = 3, dy = 4 - 0 = 4.
  • dx^2 + dy^2 = 9 + 16 = 25.
  • sqrt(25) = 5.

distance returns 5.0, matching the classic 3-4-5 right triangle.

Edge cases to consider

  • two identical points (result 0)
  • points differing only in x or only in y
  • points with negative coordinates
  • large coordinates where squaring may overflow int

Common mistakes

  • forgetting to link the math library (-lm) when using sqrt
  • squaring the difference incorrectly or omitting one axis
  • using int arithmetic and losing the fractional result

Optional extension challenge

Add a function that, given an array of points, returns the pair that are closest together.

You can re-fetch the starter code for this question: prac_q43.c.

Question 44Compass Direction Turn

(3 marks)

Estimated time: 10-15 minutes

Note prac_q44.c uses the following data type:

C
enum direction {
    NORTH,
    EAST,
    SOUTH,
    WEST
};

A robot navigation module tracks which way a robot is facing using the four compass points. Write a function that, given the current facing direction, returns the direction after turning 90 degrees clockwise (to the right).

The compass points are defined by an enum in clockwise order, so turning right advances to the next point and wraps around from WEST back to NORTH.

Your task is to complete the function enum direction turn_right(enum direction d), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q44.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q44.c:

TERMINAL
gcc -Wall -Wextra -o prac_q44 prac_q44.c
./prac_q44
0 1
EAST

./prac_q44
0 4
NORTH

./prac_q44
3 1
NORTH

Assumptions / Restrictions / Clarifications

  • The only valid directions are the four enum direction values NORTH, EAST, SOUTH and WEST.
  • Turning right from WEST wraps around to NORTH.
  • Do not print anything inside turn_right.
  • You must return an enum direction value, not a plain int printed by the function.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • enum values as small integers
  • modular wrap-around
  • returning an enum type

Worked example

For example:

For starting direction 3 (WEST) and 1 turn:

  • turn_right(WEST) advances to (3 + 1) % 4 = 0.
  • direction 0 is NORTH, so the program prints NORTH.

Edge cases to consider

  • turning right from WEST, which must wrap to NORTH
  • zero turns (direction unchanged)
  • four turns returning to the start
  • a full multiple of four turns

Common mistakes

  • forgetting the % 4 so WEST + 1 becomes an invalid direction
  • returning an int without the enum semantics of wrapping
  • turning left (subtracting) instead of right

Optional extension challenge

Add turn_left and a turn_to function that returns the fewest 90-degree turns needed to face a target direction.

You can re-fetch the starter code for this question: prac_q44.c.

Question 45Translate a Point

(3 marks)

Estimated time: 10-15 minutes

Note prac_q45.c uses the following data type:

C
typedef struct {
    int x;
    int y;
} Point;

A 2D drawing library represents positions with a Point type and needs to move points around the canvas. Write a function that returns a new Point obtained by shifting the given point by dx along the x axis and dy along the y axis.

The Point type is a typedef, so you can use it directly as Point without writing struct each time.

Your task is to complete the function Point translate(Point p, int dx, int dy), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q45.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q45.c:

TERMINAL
gcc -Wall -Wextra -o prac_q45 prac_q45.c
./prac_q45
1 2 3 4
(4, 6)

./prac_q45
0 0 -1 -1
(-1, -1)

./prac_q45
5 5 0 0
(5, 5)

Assumptions / Restrictions / Clarifications

  • Return a new Point; you do not need to modify the caller's original point.
  • dx and dy may be negative, moving the point left or down.
  • Do not print anything inside translate.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • typedef struct types
  • passing and returning a struct by value
  • field access with the . operator

Worked example

For example:

For the point (1, 2) with offsets dx = 3, dy = 4:

  • the new x is 1 + 3 = 4.
  • the new y is 2 + 4 = 6.
  • translate returns the Point (4, 6).

Edge cases to consider

  • a zero offset (point unchanged)
  • negative offsets moving the point left/down
  • offsets that move a coordinate below zero into negative space

Common mistakes

  • writing struct Point when the typedef makes Point sufficient (and there is no tag)
  • modifying the caller's copy expecting it to persist (it is passed by value)
  • swapping dx/dy or x/y in the assignment

Optional extension challenge

Add scale(Point p, int factor) and manhattan_distance(Point a, Point b) using the same Point type.

You can re-fetch the starter code for this question: prac_q45.c.

Question 46Delete The Last Node

(3 marks)

Estimated time: 15-20 minutes

Note prac_q46.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

An undo stack is stored as a linked list with the oldest action at the front. Write a function that removes the LAST node, frees it, and returns the head of the list.

Removing the last node of a one-node list leaves the list empty.

Your task is to complete the function struct node *delete_last(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q46.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q46.c:

TERMINAL
gcc -Wall -Wextra -o prac_q46 prac_q46.c
./prac_q46
4
1 2 3 4
1 2 3

./prac_q46
1
9


./prac_q46
0

Assumptions / Restrictions / Clarifications

  • Call free on the node you remove.
  • If the list is empty, return NULL.
  • If the list has exactly one node, free it and return NULL.
  • Otherwise the head does not change, so return it.
  • Do not print anything inside delete_last.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • stopping one node early in a traversal
  • looking two nodes ahead safely
  • handling empty and single-node lists separately

Edge cases to consider

  • an empty list
  • a one-node list, which becomes empty and returns NULL
  • a two-node list, the smallest case where the loop matters

Common mistakes

  • walking to the last node instead of the second-last, leaving no way to unlink it
  • forgetting to set current->next to NULL, leaving a pointer to freed memory
  • testing current->next->next without first ruling out the one-node list

Optional extension challenge

Return the value that was removed through an int pointer parameter, as well as the new head.

You can re-fetch the starter code for this question: prac_q46.c.

Question 47Count Nodes Before A Value

(3 marks)

Estimated time: 15-20 minutes

Note prac_q47.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A race result is stored as a linked list of runner numbers in finishing order. Write a function that counts how many runners finished before a given runner number.

If the number does not appear in the list, return -1.

Your task is to complete the function int count_before(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q47.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q47.c:

TERMINAL
gcc -Wall -Wextra -o prac_q47 prac_q47.c
./prac_q47
5
8 3 5 9 1
9
3

./prac_q47
4
2 4 6 8
2
0

./prac_q47
3
1 2 3
7
-1

Assumptions / Restrictions / Clarifications

  • Return -1 if the value never appears in the list.
  • Only the FIRST occurrence matters, so stop counting there.
  • If the value is in the first node, the answer is 0.
  • Do not modify the list.
  • Do not print anything inside count_before.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • combining a search with a count
  • returning mid-traversal
  • using a sentinel for the absent case

Edge cases to consider

  • the value in the first node, giving 0
  • the value absent, giving -1
  • the value appearing more than once, where only the first counts
  • an empty list

Common mistakes

  • incrementing the counter before the comparison, which is off by one
  • returning 0 rather than -1 when the value is missing, which is indistinguishable from a first-node hit
  • continuing past the first match and counting later occurrences

Optional extension challenge

Count how many nodes come AFTER the first occurrence instead.

You can re-fetch the starter code for this question: prac_q47.c.

Question 48Is A Linked List Increasing

(3 marks)

Estimated time: 15-20 minutes

Note prac_q48.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A leaderboard is stored as a linked list and should always be in strictly increasing order. Write a function that returns 1 if every value is strictly greater than the one before it, and 0 otherwise.

A list with fewer than two nodes is trivially in order, so return 1.

Your task is to complete the function int is_increasing(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q48.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q48.c:

TERMINAL
gcc -Wall -Wextra -o prac_q48 prac_q48.c
./prac_q48
5
1 3 7 8 20
1

./prac_q48
4
1 5 5 9
0

./prac_q48
0
1

Assumptions / Restrictions / Clarifications

  • Strictly increasing means equal neighbours are NOT in order, so 3 3 returns 0.
  • An empty list and a one-node list both return 1.
  • Do not modify the list.
  • Do not use an array, and do not call malloc.
  • Do not print anything inside is_increasing.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • comparing each node with its successor
  • safely looking one node ahead
  • strict versus non-strict ordering

Edge cases to consider

  • equal neighbours, which break strict order
  • an empty list and a one-node list, both in order
  • a list that only breaks order at the very last pair
  • a decreasing list

Common mistakes

  • using < instead of <= for the failure test, which wrongly accepts duplicates
  • looping while current != NULL and then dereferencing current->next on the last node
  • returning 1 from inside the loop on the first ordered pair

Optional extension challenge

Return 1 for a list that is sorted in EITHER direction, increasing or decreasing.

You can re-fetch the starter code for this question: prac_q48.c.

Question 49Delete Every Node With A Value

(3 marks)

Estimated time: 20-25 minutes

Note prac_q49.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A spam filter keeps a linked list of message IDs and needs to purge every entry matching a blocked ID. Write a function that removes ALL nodes holding a given value, frees them, and returns the head of the resulting list.

If every node matches, the result is an empty list.

Your task is to complete the function struct node *delete_all(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q49.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q49.c:

TERMINAL
gcc -Wall -Wextra -o prac_q49 prac_q49.c
./prac_q49
7
1 2 1 3 1 4 1
1
2 3 4

./prac_q49
3
5 5 5
5


./prac_q49
4
1 2 3 4
9
1 2 3 4

Assumptions / Restrictions / Clarifications

  • Every matching node must be removed, not just the first.
  • Call free on each node you remove.
  • If the list becomes empty, return NULL.
  • Matching nodes may be at the front, the end, or adjacent to each other.
  • Do not print anything inside delete_all.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • unlinking a node using the one before it
  • removing several nodes in one pass
  • advancing only when nothing was removed

Edge cases to consider

  • matches at the very front, which move the head
  • every node matching, leaving an empty list
  • two matching nodes next to each other
  • no match at all, leaving the list unchanged

Common mistakes

  • advancing the pointer after a deletion, which skips the node that moved up
  • handling front matches with the same loop as the rest and losing the head
  • freeing the node before reading its next pointer

Optional extension challenge

Return the number of nodes removed through an int pointer parameter.

You can re-fetch the starter code for this question: prac_q49.c.

Question 50Delete The Node At A Position

(3 marks)

Estimated time: 20-25 minutes

Note prac_q50.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A playlist is a linked list and the user has asked to remove the track at a given position, counting from 0. Write a function that removes and frees that node and returns the head of the list.

If the position does not exist, leave the list unchanged.

Your task is to complete the function struct node *delete_at(struct node *head, int position), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q50.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q50.c:

TERMINAL
gcc -Wall -Wextra -o prac_q50 prac_q50.c
./prac_q50
5
10 20 30 40 50
2
10 20 40 50

./prac_q50
3
1 2 3
0
2 3

./prac_q50
3
1 2 3
7
1 2 3

Assumptions / Restrictions / Clarifications

  • Positions count from 0, so position 0 removes the first node.
  • If position is past the end of the list, change nothing and return the head.
  • You may assume position is not negative.
  • Call free on the node you remove.
  • Do not print anything inside delete_at.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • stopping at the node BEFORE the one to remove
  • position 0 as a special case
  • leaving the list alone when an index is out of range

Edge cases to consider

  • position 0, which changes the head
  • the last valid position
  • a position past the end, which changes nothing
  • an empty list

Common mistakes

  • walking to the target node instead of the one before it, leaving nothing to unlink it with
  • forgetting the position 0 case and returning a head that has been freed
  • not checking for the end of the list and dereferencing NULL

Optional extension challenge

Insert a value at a given position instead, shifting the rest of the list along.

You can re-fetch the starter code for this question: prac_q50.c.

Question 51Second Largest Value In A Linked List

(3 marks)

Estimated time: 20-25 minutes

Note prac_q51.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A scoreboard is stored as a linked list and needs to show the runner-up as well as the winner. Write a function that returns the second largest DISTINCT value in the list.

If every node holds the same value there is no runner-up, so return -1.

Your task is to complete the function int second_largest(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q51.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q51.c:

TERMINAL
gcc -Wall -Wextra -o prac_q51 prac_q51.c
./prac_q51
6
4 9 2 9 7 1
7

./prac_q51
3
5 5 5
-1

./prac_q51
4
-3 -8 -1 -1
-3

Assumptions / Restrictions / Clarifications

  • Distinct means duplicates of the largest value do not count as second, so 9 9 4 gives 4.
  • If the list has fewer than two distinct values, return -1.
  • Values may be negative.
  • Do not modify the list, do not use an array, and do not call malloc.
  • Do not print anything inside second_largest.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • two passes over one list
  • tracking a best-so-far under a constraint
  • distinguishing no-answer from a real answer

Edge cases to consider

  • the largest value appearing several times
  • every value identical, which has no runner-up
  • all-negative values, where a 0 starting point would be wrong
  • a two-node list

Common mistakes

  • initialising second to 0 and returning it for an all-negative list
  • treating a duplicate of the largest as the runner-up
  • returning -1 when -1 is a legitimate answer, which this spec accepts as a known limitation

Optional extension challenge

Return the Nth largest distinct value, with N as a parameter.

You can re-fetch the starter code for this question: prac_q51.c.

Question 52Copy A Linked List

(3 marks)

Estimated time: 20-25 minutes

Note prac_q52.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

Before running a risky operation, a program wants a private copy of a linked list so the original is untouched. Write a function that builds and returns a brand new list holding the same values in the same order.

The copy must be independent: changing one list must not affect the other.

Your task is to complete the function struct node *copy_list(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q52.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q52.c:

TERMINAL
gcc -Wall -Wextra -o prac_q52 prac_q52.c
./prac_q52
4
1 2 3 4
1 2 3 4

./prac_q52
1
9
9

./prac_q52
0

Assumptions / Restrictions / Clarifications

  • Every node in the copy must be freshly allocated with malloc.
  • Do not reuse any node from the original list.
  • Copying an empty list gives an empty list, so return NULL.
  • Do not modify the original list.
  • Do not print anything inside copy_list.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • building a new list while reading another
  • keeping a tail pointer to append in order
  • the difference between copying a pointer and copying a node

Edge cases to consider

  • an empty list, which copies to NULL
  • a one-node list
  • checking the copy survives after the original is freed

Common mistakes

  • returning head, which shares nodes instead of copying them
  • building the copy in reverse by pushing each node onto the front
  • forgetting to set the last node's next to NULL

Optional extension challenge

Copy the list in reverse order instead, without a second pass.

You can re-fetch the starter code for this question: prac_q52.c.

Question 53Are Two Linked Lists Equal

(3 marks)

Estimated time: 20-25 minutes

Note prac_q53.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A sync tool compares a local list with a remote one to decide whether an upload is needed. Write a function that returns 1 if two linked lists hold exactly the same values in the same order, and 0 otherwise.

Lists of different lengths are never equal.

Your task is to complete the function int lists_equal(struct node *a, struct node *b), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q53.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q53.c:

TERMINAL
gcc -Wall -Wextra -o prac_q53 prac_q53.c
./prac_q53
3
1 2 3
3
1 2 3
1

./prac_q53
3
1 2 3
4
1 2 3 4
0

./prac_q53
0
0
1

Assumptions / Restrictions / Clarifications

  • Two empty lists are equal, so return 1.
  • Lists of different lengths are not equal, even if one is a prefix of the other.
  • Do not modify either list.
  • Do not use an array, and do not call malloc.
  • Do not print anything inside lists_equal.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • walking two lists in step
  • deciding what to do when one ends first
  • two empty lists as the base case

Edge cases to consider

  • one list being a prefix of the other, which is NOT equal
  • two empty lists, which are equal
  • same length but one differing value
  • one empty and one not

Common mistakes

  • stopping when either list ends and returning 1, which calls a prefix equal
  • comparing pointers instead of values
  • advancing only one of the two pointers

Optional extension challenge

Return 1 when one list is a prefix of the other, rather than requiring exact equality.

You can re-fetch the starter code for this question: prac_q53.c.

Question 54Join Two Linked Lists

(3 marks)

Estimated time: 20-25 minutes

Note prac_q54.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

Two queues are being merged into one. Write a function that attaches the second list to the end of the first and returns the head of the combined list.

No new nodes should be created: the existing nodes are relinked.

Your task is to complete the function struct node *join_lists(struct node *a, struct node *b), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q54.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q54.c:

TERMINAL
gcc -Wall -Wextra -o prac_q54 prac_q54.c
./prac_q54
3
1 2 3
2
8 9
1 2 3 8 9

./prac_q54
0
3
4 5 6
4 5 6

./prac_q54
2
7 8
0
7 8

Assumptions / Restrictions / Clarifications

  • Do not call malloc; reuse the existing nodes.
  • If the first list is empty, the result is simply the second list.
  • If the second list is empty, the result is the first list unchanged.
  • The result must be a single list ending in NULL.
  • Do not print anything inside join_lists.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • relinking rather than reallocating
  • walking to a tail before attaching
  • empty lists on either side

Edge cases to consider

  • an empty first list, where the answer is just the second
  • an empty second list, which changes nothing
  • both empty, giving an empty list
  • checking the joined list is freed exactly once

Common mistakes

  • looping while current != NULL and losing the node to attach to
  • returning b instead of a for a non-empty first list
  • freeing both lists separately afterwards, which double-frees the shared nodes

Optional extension challenge

Join the lists alternately instead, taking one node from each in turn.

You can re-fetch the starter code for this question: prac_q54.c.

Question 55Insert At A Position

(3 marks)

Estimated time: 20-25 minutes

Note prac_q55.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A queue system lets staff insert an urgent job partway down the list. Write a function that inserts a new node holding a given value so that it ends up at a given position, counting from 0, and returns the head.

If the position is past the end of the list, add the new node at the end.

Your task is to complete the function struct node *insert_at(struct node *head, int position, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q55.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q55.c:

TERMINAL
gcc -Wall -Wextra -o prac_q55 prac_q55.c
./prac_q55
4
1 2 4 5
2
3
1 2 3 4 5

./prac_q55
3
1 2 3
0
9
9 1 2 3

./prac_q55
2
1 2
7
8
1 2 8

Assumptions / Restrictions / Clarifications

  • Positions count from 0, so position 0 puts the new node at the front.
  • A position past the end of the list appends to the end.
  • You may assume position is not negative.
  • Allocate the new node with malloc.
  • Do not print anything inside insert_at.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • stopping at the node before an index
  • wiring a new node in without losing the tail
  • front and past-the-end as special cases

Edge cases to consider

  • position 0, which changes the head
  • a position past the end, which appends
  • inserting into an empty list
  • inserting immediately after the head

Common mistakes

  • setting current->next before saving the old link, which drops the rest of the list
  • forgetting the position 0 case and returning a head the new node is not part of
  • walking to the target index rather than one before it

Optional extension challenge

Insert so the list stays sorted, ignoring the position argument entirely.

You can re-fetch the starter code for this question: prac_q55.c.

Question 56Remove Duplicates From A Sorted Linked List

(3 marks)

Estimated time: 20-25 minutes

Note prac_q56.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A sorted list of student IDs has picked up repeats after a merge. Write a function that removes every repeated node so each value appears exactly once, and returns the head.

The list is already sorted, so equal values are always next to each other.

Your task is to complete the function struct node *remove_duplicates(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q56.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q56.c:

TERMINAL
gcc -Wall -Wextra -o prac_q56 prac_q56.c
./prac_q56
7
1 1 2 3 3 3 4
1 2 3 4

./prac_q56
3
5 5 5
5

./prac_q56
0

Assumptions / Restrictions / Clarifications

  • You may assume the list is sorted in non-decreasing order.
  • Keep the FIRST of each run of equal values and free the rest.
  • Call free on every node you remove.
  • An empty list stays empty.
  • Do not print anything inside remove_duplicates.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • exploiting sortedness to find duplicates cheaply
  • removing without a previous pointer
  • advancing only when nothing was removed

Edge cases to consider

  • a run of three or more equal values
  • every value identical, leaving one node
  • no duplicates at all
  • an empty list

Common mistakes

  • advancing after a removal, which skips the node that moved up and leaves runs of three
  • comparing current with current->next->next
  • forgetting to free the removed nodes

Optional extension challenge

Remove duplicates from an UNSORTED list, which needs a different approach entirely.

You can re-fetch the starter code for this question: prac_q56.c.

Question 57Delete The Second Last Node

(3 marks)

Estimated time: 20-25 minutes

Note prac_q57.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

An editor keeps a linked list of saved revisions and wants to discard the one just before the newest. Write a function that removes the second-last node, frees it, and returns the head.

A list with fewer than two nodes has no second-last node, so leave it unchanged.

Your task is to complete the function struct node *delete_second_last(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q57.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q57.c:

TERMINAL
gcc -Wall -Wextra -o prac_q57 prac_q57.c
./prac_q57
5
1 2 3 4 5
1 2 3 5

./prac_q57
2
7 8
8

./prac_q57
1
9
9

Assumptions / Restrictions / Clarifications

  • If the list has fewer than two nodes, change nothing.
  • In a two-node list the second-last node is the head.
  • Call free on the node you remove.
  • Do not print anything inside delete_second_last.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • looking three nodes ahead safely
  • a two-node list as its own case
  • leaving short lists untouched

Edge cases to consider

  • a two-node list, where the head is removed
  • a one-node list and an empty list, both unchanged
  • a three-node list, the smallest case where the loop runs

Common mistakes

  • dereferencing current->next->next->next without ruling out the short lists first
  • removing the last node instead of the second-last
  • forgetting that a two-node list changes the head

Optional extension challenge

Remove the Nth node from the end, with N as a parameter.

You can re-fetch the starter code for this question: prac_q57.c.

Question 58Count Distinct Values In A Linked List

(3 marks)

Estimated time: 20-25 minutes

Note prac_q58.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A survey stores every response in a linked list and needs to know how many different answers were given. Write a function that counts how many DISTINCT values appear in the list.

The list is not sorted, so equal values may be anywhere.

Your task is to complete the function int count_distinct(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q58.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q58.c:

TERMINAL
gcc -Wall -Wextra -o prac_q58 prac_q58.c
./prac_q58
6
1 2 1 3 2 4
4

./prac_q58
3
5 5 5
1

./prac_q58
0
0

Assumptions / Restrictions / Clarifications

  • A value that appears several times counts once.
  • An empty list has 0 distinct values.
  • Do not modify the list, do not use an array, and do not call malloc.
  • Do not print anything inside count_distinct.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • a nested traversal of the same list
  • counting the first occurrence only
  • using the outer pointer as a stopping point for the inner loop

Edge cases to consider

  • every value the same, giving 1
  • every value different
  • an empty list, giving 0

Common mistakes

  • running the inner loop over the whole list, so every value matches itself and nothing is counted
  • comparing against nodes AFTER the current one, which counts the last occurrence and is easy to get subtly wrong
  • forgetting to reset the seen flag for each outer node

Optional extension challenge

Return the value that appears most often, rather than the number of distinct values.

You can re-fetch the starter code for this question: prac_q58.c.

Question 59Swap The First And Last Values

(3 marks)

Estimated time: 15-20 minutes

Note prac_q59.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A rota is stored as a linked list and the first and last people on it need to trade places. Write a function that swaps the VALUES held in the first and last nodes and returns the head.

Lists with fewer than two nodes need no change.

Your task is to complete the function struct node *swap_ends(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q59.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q59.c:

TERMINAL
gcc -Wall -Wextra -o prac_q59 prac_q59.c
./prac_q59
5
1 2 3 4 5
5 2 3 4 1

./prac_q59
2
7 8
8 7

./prac_q59
1
9
9

Assumptions / Restrictions / Clarifications

  • Swap the values, not the nodes; no relinking and no malloc.
  • A list with fewer than two nodes is returned unchanged.
  • Do not call free.
  • Do not print anything inside swap_ends.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • finding the last node
  • swapping through a temporary variable
  • short lists needing no work

Edge cases to consider

  • a two-node list, where the two ends are adjacent
  • a one-node list and an empty list, both unchanged
  • a list where both ends already hold the same value

Common mistakes

  • assigning head->value = last->value first, which loses the original and sets both to the same number
  • looping while last != NULL and ending up with NULL
  • trying to swap the nodes themselves, which needs the node before the last one

Optional extension challenge

Swap the nodes themselves rather than their values, which is considerably harder.

You can re-fetch the starter code for this question: prac_q59.c.

Question 60Rotate A Linked List Forward

(3 marks)

Estimated time: 20-25 minutes

Note prac_q60.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A rota rotates each week so the person at the front moves to the back. Write a function that moves the first node to the end of the list and returns the new head.

Lists with fewer than two nodes come back unchanged.

Your task is to complete the function struct node *rotate_forward(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q60.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q60.c:

TERMINAL
gcc -Wall -Wextra -o prac_q60 prac_q60.c
./prac_q60
5
1 2 3 4 5
2 3 4 5 1

./prac_q60
2
7 8
8 7

./prac_q60
1
9
9

Assumptions / Restrictions / Clarifications

  • Move the existing node; do not call malloc and do not call free.
  • A list with fewer than two nodes is returned unchanged.
  • The rotated list must still end in NULL.
  • Do not print anything inside rotate_forward.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • relinking three pointers in the right order
  • finding the tail before changing anything
  • why the old head must end in NULL

Edge cases to consider

  • a two-node list, where the ends simply trade places
  • a one-node list and an empty list, both unchanged
  • checking the list still terminates and has not become a loop

Common mistakes

  • forgetting head->next = NULL, which leaves a cycle and makes printing loop forever
  • setting last->next before saving head->next, losing the new head
  • walking the tail search after the links have already been changed

Optional extension challenge

Rotate the list backward instead, moving the LAST node to the front.

You can re-fetch the starter code for this question: prac_q60.c.

Question 61Struct Array: Student Top Scorer

(4 marks)

Estimated time: 20-30 minutes

Note prac_q61.c uses the following data type:

C
struct student {
    char name[50];
    int mark;
};

A course convenor wants a quick way to find the top-performing student in a cohort based on their exam results. A struct student (see the provided starter header) stores a name and an integer mark out of 100. Write a function that, given the students array containing n students, returns the index of the student with the highest mark.

If multiple student records are tied for the highest mark, return the index of the first such student (i.e. the one with the smallest index) so the result is deterministic.

Your task is to complete the function int top_scorer(struct student students[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q61.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q61.c:

TERMINAL
gcc -Wall -Wextra -o prac_q61 prac_q61.c
./prac_q61
3
Ada 88
Grace 95
Alan 95
1

./prac_q61
1
Katherine 91
0

./prac_q61
3
Bob 60
Cy 72
Dee 99
2

Assumptions / Restrictions / Clarifications

  • You may assume n is greater than 0; the function is never called on an empty array.
  • Marks are integers between 0 and 100 inclusive.
  • If several student records share the highest mark, the earliest one in the array (smallest index) must be returned.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • arrays of structs
  • tracking the best record while scanning
  • returning a record or an index

Worked example

For example:

For students containing Ada 88, Grace 95, Alan 95:

  • best starts at index 0 (Ada, 88).
  • index 1 (Grace, 95) has a higher mark than 88, so best becomes 1.
  • index 2 (Alan, 95) is not strictly greater than 95, so best stays 1.

top_scorer returns 1, the first student to reach the highest mark.

Edge cases to consider

  • a single student
  • two student records tied for the top score
  • all student records having the same score
  • scores with large differences

Common mistakes

  • comparing the wrong field (name vs score)
  • returning a copy that goes stale, or an index that is off by one
  • not defining tie-break behaviour, so results are non-deterministic

Optional extension challenge

Return the top k students (by score) sorted descending, not just the single best.

You can re-fetch the starter code for this question: prac_q61.c.

Question 62Anagram Checker

(4 marks)

Estimated time: 25-35 minutes

A word game wants to let players check whether the letters they have rearranged form a valid anagram of a target word. Write a function that determines whether two null-terminated strings consisting only of lowercase letters are anagrams of each other (i.e. one is a rearrangement of the letters of the other), returning 1 if so and 0 otherwise.

Both strings must use exactly the same multiset of letters, meaning each letter must appear the same number of times in both strings, not just the same set of distinct letters.

Your task is to complete the function int is_anagram(char *s1, char *s2), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q62.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q62.c:

TERMINAL
gcc -Wall -Wextra -o prac_q62 prac_q62.c
./prac_q62
listen
silent
1

./prac_q62
hello
world
0

./prac_q62
aabbcc
abcabc
1

Assumptions / Restrictions / Clarifications

  • Both strings contain only lowercase letters 'a'-'z' (no spaces or punctuation).
  • Strings of different lengths can never be anagrams of each other.
  • Repeated letters matter: "aab" and "abb" are not anagrams of each other.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • character frequency counting
  • fixed-size tally arrays
  • comparing two strings structurally

Worked example

For example:

For s1 = "listen" and s2 = "silent":

  • counting each letter of "listen" gives +1 to l, i, s, t, e, n.
  • counting each letter of "silent" subtracts 1 from s, i, l, e, n, t.
  • every letter's net count is 0.

is_anagram returns 1.

Edge cases to consider

  • two empty strings (trivially anagrams)
  • strings of different lengths (cannot be anagrams)
  • case differences and whether they matter
  • repeated letters that must be counted, not just matched once

Common mistakes

  • comparing sorted copies but mutating the caller's strings
  • using a 26-slot table but feeding it non-letters out of range
  • declaring equal after matching only the letters present in one string

Optional extension challenge

Ignore spaces and punctuation and treat upper/lowercase as equal so that "Dormitory" and "Dirty room" match.

You can re-fetch the starter code for this question: prac_q62.c.

Question 63Run-Length Encode

(4 marks)

Estimated time: 25-35 minutes

A basic image/fax compression scheme reduces long runs of the same symbol to a shorter representation. Write a function that performs simple run-length encoding of a null-terminated string made up of uppercase letters only, writing the encoded result into a buffer supplied by the caller.

Each maximal run of a repeated character c of length k is written as the character c followed by the decimal digits of k. The buffer is guaranteed to be large enough to hold the result plus a null terminator.

Your task is to complete the function void run_length_encode(char *input, char *output), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q63.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q63.c:

TERMINAL
gcc -Wall -Wextra -o prac_q63 prac_q63.c
./prac_q63
AAABCCDD
A3B1C2D2

./prac_q63
A
A1

./prac_q63
ABCD
A1B1C1D1

./prac_q63

Assumptions / Restrictions / Clarifications

  • input contains only uppercase letters 'A'-'Z' and is null-terminated.
  • A run of length 1 is still written with the digit '1' (e.g. "A" encodes as "A1").
  • output is a caller-supplied buffer guaranteed to be large enough to hold the encoded result plus a null terminator; you must null-terminate output.
  • input may be the empty string, in which case output should also be the empty string.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • scanning for runs of equal characters
  • building the string in the output buffer
  • number-to-text formatting

Worked example

For example:

For input = "AAABCCDD":

  • the run of 'A' has length 3, written as "A3".
  • the run of 'B' has length 1, written as "B1".
  • the run of 'C' has length 2, written as "C2".
  • the run of 'D' has length 2, written as "D2".

output is "A3B1C2D2".

Edge cases to consider

  • an empty string in input
  • a string with no repeats (every run length 1)
  • a very long run (length crossing 9 into multiple digits)
  • a single character

Common mistakes

  • writing the run length before the character or vice versa inconsistently
  • off-by-one in the run counter when the run ends at the string terminator
  • forgetting to null-terminate the output buffer

Optional extension challenge

Support decoding as well: turn "4a3b" back into "aaaabbb", validating the encoded form as you go.

You can re-fetch the starter code for this question: prac_q63.c.

Question 64Longest Common Prefix

(4 marks)

Estimated time: 25-35 minutes

An autocomplete feature wants to display the common starting text shared by all the matching suggestions for what a user has typed so far. Write a function that finds the longest common prefix shared by all strings in an array of n null-terminated strings, writing it (null-terminated) into a buffer supplied by the caller.

If there is no common prefix, or n is 0, the buffer should contain the empty string.

Your task is to complete the function void longest_common_prefix(char *strs[], int n, char *result), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q64.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q64.c:

TERMINAL
gcc -Wall -Wextra -o prac_q64 prac_q64.c
./prac_q64
3
flower
flow
flight
fl

./prac_q64
2
dog
cat


./prac_q64
2
test
test
test

Assumptions / Restrictions / Clarifications

  • The result buffer is guaranteed to be large enough to hold the longest possible prefix plus a null terminator.
  • Comparison is case-sensitive.
  • n may be 0, in which case result should be set to the empty string.
  • If one of the strings is itself a prefix of all the others (e.g. the shortest string), the common prefix can be no longer than that string.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • comparing multiple strings column by column
  • early termination
  • copying a prefix out

Worked example

For example:

For strs = ["flower", "flow", "flight"]:

  • position 0: all three strings have 'f' -- matches, prefix so far "f".
  • position 1: all three have 'l' -- matches, prefix so far "fl".
  • position 2: "flower" and "flow" have 'o', but "flight" has 'i' -- mismatch.

The function stops there, so result is "fl".

Edge cases to consider

  • an array containing the empty string (prefix must be empty)
  • all strings identical
  • a single string (the whole string is the prefix)
  • no common prefix at all

Common mistakes

  • reading past the end of the shortest string
  • comparing pairs instead of the same column across all strings
  • forgetting to terminate the output prefix

Optional extension challenge

Return the longest common suffix instead, or find the pair of strings with the longest shared prefix.

You can re-fetch the starter code for this question: prac_q64.c.

Question 65Matrix Transpose

(4 marks)

Estimated time: 25-35 minutes

A small linear-algebra library needs a matrix-transposition routine as part of larger computations. Write a function that transposes a rows x cols 2D array of integers, storing the result (a cols x rows array) into a second 2D array supplied by the caller.

Transposition swaps rows and columns, so input[i][j] becomes output[j][i]. For example, a 2x3 matrix becomes a 3x2 matrix after transposing.

Your task is to complete the function void transpose(int input[10][10], int output[10][10], int rows, int cols), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q65.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q65.c:

TERMINAL
gcc -Wall -Wextra -o prac_q65 prac_q65.c
./prac_q65
2 3
1 2 3
4 5 6
1 4
2 5
3 6

./prac_q65
2 2
1 2
3 4
1 3
2 4

./prac_q65
1 3
5 6 7
5
6
7

Assumptions / Restrictions / Clarifications

  • You may assume rows and cols are each at most 10, matching the fixed array size.
  • input and output are separate arrays; do not attempt the operation in place.
  • Only the first rows x cols region of input is meaningful; the rest may contain garbage and should be ignored.
  • Only the first cols x rows region of output needs to be written.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • 2D array indexing
  • mapping [i][j] to [j][i]
  • distinguishing the rows count from the column count

Worked example

For example:

For the 2x3 matrix [[1,2,3],[4,5,6]]:

  • output[0][0] = input[0][0] = 1, output[0][1] = input[1][0] = 4.
  • output[1][0] = input[0][1] = 2, output[1][1] = input[1][1] = 5.
  • output[2][0] = input[0][2] = 3, output[2][1] = input[1][2] = 6.

The printed 3x2 result is "1 4", "2 5", "3 6".

Edge cases to consider

  • a non-square matrix (dimensions swap)
  • a 1xN or Nx1 matrix
  • a 1x1 matrix
  • a square matrix where an in-place operation is tempting

Common mistakes

  • swapping in place across the whole matrix and undoing every swap
  • mixing up the row and column bounds for a non-square matrix
  • writing to out[i][j] instead of out[j][i]

Optional extension challenge

Transpose a square matrix in place (no second matrix) by swapping only the upper triangle.

You can re-fetch the starter code for this question: prac_q65.c.

Question 66Linked List Sum

(4 marks)

Estimated time: 25-35 minutes

Note prac_q66.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A shopping cart is implemented internally as a singly linked list of item prices (as integer cents), and the checkout screen needs to display the running total. A singly linked list of integers is defined by the provided struct node. Write a function that returns the sum of all values stored in the list.

An empty list (head == NULL) has a sum of 0, representing an empty cart.

Your task is to complete the function int list_sum(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q66.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q66.c:

TERMINAL
gcc -Wall -Wextra -o prac_q66 prac_q66.c
./prac_q66
4
3 8 1 5
17

./prac_q66
0
0

./prac_q66
1
42
42

Assumptions / Restrictions / Clarifications

  • Do not modify the list; the traversal should be read-only.
  • The list may be arbitrarily long.
  • Values stored in the list may be negative, zero, or positive.
  • head may be NULL, representing an empty list.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • linked list traversal
  • following next pointers to NULL
  • accumulating over nodes

Worked example

For example:

For the list 3 -> 8 -> 1 -> 5 -> NULL:

  • sum starts at 0.
  • visiting each node in turn adds 3, then 8, then 1, then 5: 0+3=3, 3+8=11, 11+1=12, 12+5=17.

list_sum returns 17.

Edge cases to consider

  • an empty list (head == NULL), sum 0
  • a single-node list
  • a list containing negative data values
  • a long list

Common mistakes

  • dereferencing a NULL head before checking it
  • stopping one node early or looping forever by not advancing the cursor
  • modifying the list while summing it

Optional extension challenge

Also return the number of nodes and compute the average, all in a single traversal.

You can re-fetch the starter code for this question: prac_q66.c.

Question 67Merge Two Sorted Arrays

(4 marks)

Estimated time: 25-35 minutes

Two separate leaderboards have each been sorted in ascending order of score, and you need to combine them into a single sorted leaderboard without re-sorting everything from scratch. Write a function that merges two arrays of integers, each already sorted in ascending order, into a single sorted array.

The caller supplies a result array guaranteed to be large enough to hold n1 + n2 elements. Your function should run in a single linear pass over both input arrays, taking advantage of the fact that they are already sorted, rather than concatenating and re-sorting.

Your task is to complete the function void merge_sorted(int a[], int n1, int b[], int n2, int result[]), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q67.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q67.c:

TERMINAL
gcc -Wall -Wextra -o prac_q67 prac_q67.c
./prac_q67
3
1 4 7
4
2 3 8 9
1 2 3 4 7 8 9

./prac_q67
2
5 6
0
5 6

./prac_q67
3
2 2 5
2
2 6
2 2 2 5 6

Assumptions / Restrictions / Clarifications

  • Both a and b are guaranteed to already be sorted in ascending order.
  • Either array may be empty (n1 or n2 equal to 0).
  • Duplicate values, whether within one array or across the two arrays, should all be kept in result.
  • result is guaranteed to be large enough to hold n1 + n2 elements.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • two-pointer merge
  • preserving sorted order
  • writing into an output array

Worked example

For example:

For a = [1, 4, 7] and b = [2, 3, 8, 9]:

  • compare 1 and 2: 1 is smaller, take 1.
  • compare 4 and 2: 2 is smaller, take 2.
  • compare 4 and 3: 3 is smaller, take 3.
  • compare 4 and 8: 4 is smaller, take 4.
  • compare 7 and 8: 7 is smaller, take 7.
  • a is now exhausted, so the remaining elements of b (8, 9) are appended.

result is "1 2 3 4 7 8 9".

Edge cases to consider

  • one array empty and the other not
  • both arrays empty
  • arrays with duplicate values across both inputs
  • all elements of one array smaller than the other

Common mistakes

  • forgetting to copy the leftover tail of the longer array
  • advancing both indices when only one element was consumed
  • writing past the end of the output array

Optional extension challenge

Merge the two arrays in place into the first one, assuming it has enough spare capacity at the end.

You can re-fetch the starter code for this question: prac_q67.c.

Question 68Rotate Array Left

(4 marks)

Estimated time: 25-35 minutes

A circular buffer used in a music player's queue of upcoming tracks needs to be rotated whenever the currently playing track changes, so that the queue always starts from the correct position. Write a function that rotates the elements of an array of n integers to the left by k positions in place, so that the element originally at index k becomes the new element at index 0, wrapping around.

You must not allocate a second array the same size as arr to perform the rotation; the array should be updated in place.

Your task is to complete the function void rotate_left(int arr[], int n, int k), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q68.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q68.c:

TERMINAL
gcc -Wall -Wextra -o prac_q68 prac_q68.c
./prac_q68
5
1 2 3 4 5
2
3 4 5 1 2

./prac_q68
3
1 2 3
7
2 3 1

./prac_q68
3
9 8 7
0
9 8 7

Assumptions / Restrictions / Clarifications

  • 0 <= k, and k may be larger than n (in which case reduce it with k % n).
  • n may be 0, in which case the function does nothing.
  • k may be 0, in which case the array is left unchanged.
  • The relative order of elements is preserved; only their starting position changes.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • modular index arithmetic
  • rotation by k positions
  • handling k larger than n

Worked example

For example:

For arr = [1, 2, 3, 4, 5] and k = 2:

  • the element originally at index 2 (value 3) becomes the new index 0.
  • reading two positions ahead (wrapping around) for every index gives [3, 4, 5, 1, 2].

The array printed by main is "3 4 5 1 2".

Edge cases to consider

  • k == 0 (no change)
  • k equal to or a multiple of n (no change)
  • k greater than n (reduce with % n)
  • an empty or single-element array

Common mistakes

  • not reducing k modulo n, causing out-of-bounds indexing
  • rotating right instead of left
  • overwriting elements before they have been copied when done naively

Optional extension challenge

Rotate using the three-reversal trick (reverse parts, then the whole) to achieve O(1) extra space.

You can re-fetch the starter code for this question: prac_q68.c.

Question 69Caesar Cipher

(4 marks)

Estimated time: 20-30 minutes

A retro cryptography demo wants to reproduce one of the oldest known ciphers, in which every letter of a message is shifted a fixed number of positions through the alphabet. Write a function that applies a Caesar cipher shift to a null-terminated string consisting only of lowercase letters, shifting every letter forward in the alphabet by shift positions, wrapping around from 'z' back to 'a'.

The string should be modified in place; the function does not return a new string or allocate any memory.

Your task is to complete the function void caesar_shift(char *s, int shift), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q69.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q69.c:

TERMINAL
gcc -Wall -Wextra -o prac_q69 prac_q69.c
./prac_q69
hello
3
khoor

./prac_q69
xyz
3
abc

./prac_q69
abc
0
abc

Assumptions / Restrictions / Clarifications

  • 0 <= shift && shift <= 25.
  • The string contains only lowercase letters 'a'-'z' (no spaces or punctuation).
  • Shifting must wrap around correctly, e.g. 'z' shifted by 1 becomes 'a'.
  • A shift of 0 should leave the string unchanged.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • character arithmetic on letters
  • modular wrap-around within lowercase letters
  • applying uniform transformations to text

Worked example

For example:

For s = "xyz" and shift = 3:

  • 'x' is 23 positions after 'a'; (23 + 3) % 26 = 0, giving 'a'.
  • 'y' is 24 positions after 'a'; (24 + 3) % 26 = 1, giving 'b'.
  • 'z' is 25 positions after 'a'; (25 + 3) % 26 = 2, giving 'c'.

s becomes "abc", demonstrating the wrap-around from 'z' back to 'a'.

Edge cases to consider

  • a shift of 0 (unchanged text)
  • a shift of 25 (maximum allowed shift)
  • wrapping past z back to a
  • strings with repeated identical characters

Common mistakes

  • applying % 26 to the raw ASCII code instead of the letter offset
  • off-by-one in the alphabet boundary check
  • forgetting to null-terminate or preserve string length

Optional extension challenge

Write the decryption function caesar_decrypt(char *s, int shift) that reverses the transformation.

You can re-fetch the starter code for this question: prac_q69.c.

Question 70Struct Array: Total Inventory Value

(4 marks)

Estimated time: 25-35 minutes

Note prac_q70.c uses the following data type:

C
struct item {
    char name[50];
    int quantity;
    double unit_price;
};

A small warehouse management system needs to report the total dollar value of everything currently in stock. A struct item (see the provided starter header) stores a name, an integer quantity, and a double unit_price.

Write a function that computes the total value of an inventory (an items array containing n records), summing quantity * unit_price across all records.

Your task is to complete the function double total_value(struct item items[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q70.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q70.c:

TERMINAL
gcc -Wall -Wextra -o prac_q70 prac_q70.c
./prac_q70
2
Widget 10 2.50
Gadget 3 15.00
70

./prac_q70
0
0

./prac_q70
1
OutOfStock 0 9.99
0

Assumptions / Restrictions / Clarifications

  • n may be 0, in which case the function returns 0.0.
  • quantity is always non-negative; unit_price is always non-negative.
  • The result should be returned as a double to correctly represent cents.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • arrays of structs
  • accumulating a floating-point total
  • quantity times price per item

Worked example

For example:

For items containing Widget (qty 10, $2.50) and Gadget (qty 3, $15.00):

  • Widget contributes 10 * 2.50 = 25.0.
  • Gadget contributes 3 * 15.00 = 45.0.
  • 25.0 + 45.0 = 70.0.

total_value returns 70.0.

Edge cases to consider

  • an empty inventory (total 0.0)
  • an item with quantity 0
  • a single item
  • prices that accumulate floating-point rounding

Common mistakes

  • summing price without multiplying by quantity
  • using int for the running total and truncating cents
  • iterating past the number of items actually present

Optional extension challenge

Also return the single most valuable line item (quantity times price) via an output parameter.

You can re-fetch the starter code for this question: prac_q70.c.

Question 71Bubble Sort Descending

(4 marks)

Estimated time: 25-35 minutes

As part of an algorithms unit, you are asked to implement a classic sorting algorithm by hand rather than relying on a library function. Write a function that sorts an array of n integers into descending order in place, using the bubble sort algorithm (repeatedly stepping through the array, swapping adjacent elements that are in the wrong order).

The sort must continue making passes over the array until no more swaps are needed, at which point the array is fully sorted in descending order.

Your task is to complete the function void bubble_sort_desc(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q71.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q71.c:

TERMINAL
gcc -Wall -Wextra -o prac_q71 prac_q71.c
./prac_q71
8
3 1 4 1 5 9 2 6
9 6 5 4 3 2 1 1

./prac_q71
3
5 3 1
5 3 1

./prac_q71
1
7
7

Assumptions / Restrictions / Clarifications

  • You must implement bubble sort specifically (repeated adjacent swaps); do not call a library sort function such as qsort.
  • n may be 0 or 1, in which case the array is already trivially sorted.
  • arr may contain duplicate values, which should remain adjacent to each other after sorting.
  • The sort must be performed in place, without allocating a second array.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • nested-loop sorting
  • adjacent swaps
  • descending vs ascending comparison direction

Worked example

For example:

For arr = [3, 1, 4, 1, 5, 9, 2, 6]:

  • each pass compares adjacent elements and swaps them if the left one is smaller than the right one.
  • after enough passes, larger values 'bubble' to the front and smaller values sink to the back.
  • once a full pass makes no swaps, the array is fully sorted.

The final descending order is "9 6 5 4 3 2 1 1".

Edge cases to consider

  • an already-sorted (descending) array
  • an array sorted the wrong way (fully reversed)
  • an array with duplicate values
  • a single-element or empty array

Common mistakes

  • using the ascending comparison and sorting the wrong way
  • off-by-one in the inner loop bound reading past the end
  • swapping indices rather than the values at those indices

Optional extension challenge

Add an early-exit optimisation that stops once a full pass makes no swaps, and count the swaps performed.

You can re-fetch the starter code for this question: prac_q71.c.

Question 72Remove Duplicates From Sorted Array

(4 marks)

Estimated time: 25-35 minutes

A sensor logs readings into a sorted array over time, but occasionally records the same reading twice in a row and you need to clean this up in place without using extra memory. Write a function that removes duplicate values from an array of n integers that is already sorted in ascending order, compacting the unique values to the front of the array (in order) and returning the count of unique values.

The contents of the array beyond the returned count are unspecified and will not be checked; only arr[0] up to arr[returned_count - 1] need to be correct.

Your task is to complete the function int remove_duplicates(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q72.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q72.c:

TERMINAL
gcc -Wall -Wextra -o prac_q72 prac_q72.c
./prac_q72
7
1 1 2 3 3 3 5
4
1 2 3 5

./prac_q72
3
2 4 6
3
2 4 6

./prac_q72
0
0

Assumptions / Restrictions / Clarifications

  • n may be 0, in which case the function should return 0.
  • The array is guaranteed to be sorted ascending on entry, so duplicates are always adjacent.
  • You must modify arr in place; do not allocate a second array.
  • The relative order of the unique values must be preserved.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • in-place compaction with a write index
  • exploiting sorted order
  • returning the new length

Worked example

For example:

For arr = [1, 1, 2, 3, 3, 3, 5]:

  • write starts at 1 (arr[0] = 1 is always kept).
  • read = 1: arr[1] = 1 equals arr[write - 1] = 1, skip.
  • read = 2: arr[2] = 2 differs from 1, write it at index 1, so write becomes 2.
  • read = 3, 4, 5: 3 is written once (index 2), and the two further 3s are skipped.
  • read = 6: 5 differs from 3, is written at index 3, and write becomes 4.

remove_duplicates returns 4, and arr[0..3] is "1 2 3 5".

Edge cases to consider

  • an empty array (new length 0)
  • an array with no duplicates
  • an array where every element is identical (new length 1)
  • duplicates only at the very end

Common mistakes

  • using a second array instead of compacting in place
  • comparing non-adjacent elements and missing runs
  • returning the old length or an off-by-one new length

Optional extension challenge

Instead of keeping one copy, keep only elements that appear exactly once, removing all duplicated values entirely.

You can re-fetch the starter code for this question: prac_q72.c.

Question 73Balanced Brackets

(4 marks)

Estimated time: 25-35 minutes

A code editor's syntax highlighter needs to check whether the brackets in a snippet of source code are correctly matched before attempting to parse it further. Write a function that determines whether the brackets in a null-terminated string are balanced.

The string may contain the bracket characters '(', ')', '[', ']', '{', '}' interspersed with other characters, which should be ignored. Brackets are balanced if every opening bracket has a matching closing bracket of the same type, correctly nested, in the correct order. Return 1 if balanced, 0 otherwise. You should implement this using an array as a stack (no library stack/queue is provided).

Your task is to complete the function int is_balanced(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q73.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q73.c:

TERMINAL
gcc -Wall -Wextra -o prac_q73 prac_q73.c
./prac_q73
a(b[c]d)e
1

./prac_q73
(a[b)c]
0

./prac_q73
{[()]}
1

./prac_q73
(
0

Assumptions / Restrictions / Clarifications

  • You may assume the string is at most 999 characters long.
  • Non-bracket characters (letters, digits, punctuation) do not affect the result.
  • Brackets of different types must nest correctly (e.g. "(]" is not balanced even though each bracket type individually appears once).
  • The empty string is considered balanced.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • stack-based matching
  • pushing openers and popping on closers
  • detecting mismatches and leftovers

Worked example

For example:

For s = "(a[b)c]":

  • '(' is pushed. stack: (
  • 'a' is ignored.
  • '[' is pushed. stack: ( [
  • 'b' is ignored.
  • ')' is seen, so the top of the stack is popped: it is '[', not '(', which is a mismatch.

is_balanced returns 0 as soon as this mismatch is detected.

Edge cases to consider

  • the empty string (balanced)
  • a closing bracket with nothing open (immediate failure)
  • correct counts but wrong nesting order, e.g. "([)]"
  • unclosed openers left on the stack at the end

Common mistakes

  • only counting brackets instead of checking nesting order
  • forgetting to fail when the stack is empty on a closer
  • declaring balanced while openers remain unmatched at the end

Optional extension challenge

Support three bracket kinds ((), [], {}) and report the index at which the first mismatch occurs.

You can re-fetch the starter code for this question: prac_q73.c.

Question 74Compress Whitespace

(4 marks)

Estimated time: 25-35 minutes

A form-submission handler wants to tidy up freeform text fields that users have typed messily, with stray leading, trailing, or doubled-up spaces. Write a function that collapses every run of one or more consecutive space characters in a null-terminated string into a single space, and removes any leading or trailing spaces, modifying the string in place.

Because the resulting string is always the same length as or shorter than the original, this can be done in place without needing a second buffer.

Your task is to complete the function void compress_whitespace(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q74.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q74.c:

TERMINAL
gcc -Wall -Wextra -o prac_q74 prac_q74.c
./prac_q74
  hello   world  
hello world

./prac_q74
already fine
already fine

./prac_q74
     

Assumptions / Restrictions / Clarifications

  • Only the space character ' ' is treated as whitespace (not tabs or newlines).
  • The resulting string is always no longer than the original.
  • A string that is empty, or contains only spaces, becomes the empty string after compressing.
  • The string must be modified in place; no new buffer is allocated or returned.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • in-place string rewriting
  • collapsing runs of spaces
  • trimming leading/trailing spaces

Worked example

For example:

For s = " hello world ":

  • the two leading spaces are skipped entirely.
  • "hello" is copied across unchanged.
  • the run of three spaces is collapsed to a single space.
  • "world" is copied across unchanged.
  • the two trailing spaces are removed at the end.

s becomes "hello world".

Edge cases to consider

  • the empty string
  • a string that is all spaces (becomes empty)
  • leading and trailing spaces to trim
  • multiple internal runs of spaces

Common mistakes

  • leaving a single leading or trailing space behind
  • collapsing runs but forgetting to re-terminate the shortened string
  • using a read index and write index that get out of sync

Optional extension challenge

Also collapse tabs and newlines into single spaces, and normalise any run of mixed whitespace to one space.

You can re-fetch the starter code for this question: prac_q74.c.

Question 75Grid Neighbour Sum

(4 marks)

Estimated time: 25-35 minutes

A cellular-automaton simulation (in the spirit of Conway's Game of Life) needs to compute a value for each cell based on its immediate neighbours before updating the grid. Given a rows x cols 2D array of integers representing a grid, write a function that computes, for a given cell (r, c), the sum of the values of its orthogonal neighbours (up, down, left, right) that lie within the grid bounds.

Neighbours that fall outside the grid (for cells on an edge or corner) are simply not included in the sum, rather than being treated as zero or causing an error.

Your task is to complete the function int neighbour_sum(int grid[10][10], int rows, int cols, int r, int c), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q75.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q75.c:

TERMINAL
gcc -Wall -Wextra -o prac_q75 prac_q75.c
./prac_q75
3 3
1 2 3
4 5 6
7 8 9
1 1
20

./prac_q75
3 3
1 2 3
4 5 6
7 8 9
0 0
6

./prac_q75
3 3
1 2 3
4 5 6
7 8 9
0 1
9

Assumptions / Restrictions / Clarifications

  • 0 <= r && r < rows and 0 <= c && c < cols.
  • rows and cols are each at most 10, matching the fixed array size.
  • Diagonal neighbours are not included, only up/down/left/right.
  • Corner and edge cells have fewer than 4 in-bounds neighbours; only the ones that exist should be summed.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • 2D neighbourhood iteration
  • boundary/edge handling
  • summing the four orthogonal neighbours

Worked example

For example:

For grid = [[1,2,3],[4,5,6],[7,8,9]] and cell (1,1) (the centre, value 5):

  • up neighbour (0,1) = 2.
  • down neighbour (2,1) = 8.
  • left neighbour (1,0) = 4.
  • right neighbour (1,2) = 6.
  • all four neighbours are in bounds: 2 + 8 + 4 + 6 = 20.

neighbour_sum returns 20.

Edge cases to consider

  • a corner cell (only two orthogonal neighbours)
  • an edge cell (only three orthogonal neighbours)
  • a 1x1 grid (no neighbours)
  • excluding the cell itself from its own neighbour sum

Common mistakes

  • reading outside the grid at edges and corners
  • including the centre cell in its own sum
  • using signed offsets without clamping to valid indices

Optional extension challenge

Compute one Game of Life step for the whole grid, deriving each new cell from its neighbour count.

You can re-fetch the starter code for this question: prac_q75.c.

Question 76Second Largest in Array

(4 marks)

Estimated time: 20-30 minutes

A leaderboard wants to display not just the winner but also the runner-up score. Write a function that returns the second largest DISTINCT value in an array of n integers.

If every element is equal (so there is no distinct second value), return that single value.

Your task is to complete the function int second_largest(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q76.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q76.c:

TERMINAL
gcc -Wall -Wextra -o prac_q76 prac_q76.c
./prac_q76
8
3 1 4 1 5 9 2 6
6

./prac_q76
3
7 7 7
7

./prac_q76
2
10 20
10

Assumptions / Restrictions / Clarifications

  • You may assume n >= 2.
  • Duplicates of the maximum do not count as the second largest; you want the largest value strictly less than the maximum.
  • If all elements are equal, return that value.
  • Do not modify the input array and do not print inside second_largest.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • two passes over an array
  • handling duplicate maxima
  • a found/sentinel flag

Worked example

For example:

For the array 3 1 4 1 5 9 2 6:

  • the maximum value is 9.
  • the largest value strictly less than 9 is 6.
  • second_largest therefore returns 6.

Edge cases to consider

  • all elements equal (return that value)
  • several copies of the maximum
  • exactly two elements
  • negative values

Common mistakes

  • returning the second position after a naive sort rather than the second distinct value
  • initialising second to 0, which breaks for all-negative arrays
  • counting a duplicate of the maximum as the second largest

Optional extension challenge

Return the k-th largest distinct value for a k passed as a parameter, without fully sorting the array.

You can re-fetch the starter code for this question: prac_q76.c.

Question 77Debug and Fix: Array Sum

(4 marks)

Estimated time: 15-25 minutes

The starter file for this question contains a function that is SUPPOSED to return the sum of the first n elements of an array, but it is deliberately broken: it contains two bugs. Your task is to find and fix them so the function returns the correct sum.

This is a debugging exercise. Study the given implementation, work out why its output is wrong, and repair it. Do not rewrite the function from scratch unless you need to; the goal is to understand the specific mistakes.

The two bugs to look for are: the accumulator is initialised incorrectly, and the loop bounds are off by one (it skips the first element and reads one element past the end of the meaningful data).

Your task is to complete the function int sum_array(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q77.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q77.c:

TERMINAL
gcc -Wall -Wextra -o prac_q77 prac_q77.c
./prac_q77
4
1 2 3 4
10

./prac_q77
2
-5 5
0

./prac_q77
0
0

Assumptions / Restrictions / Clarifications

  • After your fix, the function must return 0 when n is 0.
  • The corrected function must read exactly the elements arr[0] through arr[n - 1].
  • Do not change the function's prototype or the main function.
  • Do not print anything inside sum_array.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • reading and understanding unfamiliar code
  • spotting off-by-one loop errors
  • accumulator initialisation

Worked example

For example:

For the array 1 2 3 4 (after fixing):

  • total starts at 0 and each of arr[0..3] is added: 1 + 2 + 3 + 4.
  • the correct sum is 10, so the fixed sum_array returns 10.

The unfixed starter would instead start at 1 and skip arr[0] while reading past the end, giving a wrong answer.

Edge cases to consider

  • n == 0, which must return 0 not 1
  • a single-element array
  • arrays whose values cancel to 0
  • negative values

Common mistakes

  • fixing only one of the two bugs
  • leaving total initialised to 1
  • changing i <= n to i < n but forgetting to also start i at 0

Optional extension challenge

Turn the corrected code into a function that returns both the sum and the count of positive elements via output parameters.

You can re-fetch the starter code for this question: prac_q77.c.

Question 78Traffic Light Controller

(4 marks)

Estimated time: 15-20 minutes

Note prac_q78.c uses the following data type:

C
typedef enum {
    RED,
    GREEN,
    YELLOW
} TrafficLight;

A pedestrian-crossing controller advances a traffic light through its cycle. Write a function that, given the current light, returns the next light in the sequence RED then GREEN then YELLOW and back to RED.

The TrafficLight type is defined with a typedef enum, so you can use it directly as TrafficLight.

Your task is to complete the function TrafficLight next_light(TrafficLight current), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q78.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q78.c:

TERMINAL
gcc -Wall -Wextra -o prac_q78 prac_q78.c
./prac_q78
0 1
GREEN

./prac_q78
0 3
RED

./prac_q78
1 2
RED

Assumptions / Restrictions / Clarifications

  • The only valid values are RED, GREEN and YELLOW.
  • After YELLOW the cycle returns to RED.
  • Return a TrafficLight value; do not print inside next_light.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • typedef enum types
  • switch statements over enum values
  • cyclic state transitions

Worked example

For example:

For starting light 1 (GREEN) and 2 steps:

  • next_light(GREEN) returns YELLOW.
  • next_light(YELLOW) returns RED.
  • after two steps the light is RED, so the program prints RED.

Edge cases to consider

  • advancing from YELLOW, which wraps to RED
  • zero steps (light unchanged)
  • three steps returning to the start
  • a step count that is a multiple of three

Common mistakes

  • falling through switch cases by omitting return/break
  • hard-coding integer arithmetic that produces an out-of-range value
  • forgetting the wrap from YELLOW back to RED

Optional extension challenge

Add a flashing-amber fault mode as a fourth state and a function that decides when to enter or leave it.

You can re-fetch the starter code for this question: prac_q78.c.

Question 79Sum Every k-th Element (Pointer Arithmetic)

(4 marks)

Estimated time: 20-30 minutes

A sampling routine wants to sum every k-th reading from a data buffer, starting from the first. Write a function that returns the sum of the elements at indices 0, k, 2k, 3k, ..., up to (but not including) index n.

You must walk the array using POINTER ARITHMETIC (advancing a pointer by k each step) rather than indexing with square brackets.

Your task is to complete the function int sum_stride(int *arr, int n, int k), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q79.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q79.c:

TERMINAL
gcc -Wall -Wextra -o prac_q79 prac_q79.c
./prac_q79
5
10 20 30 40 50
2
90

./prac_q79
3
5 5 5
1
15

./prac_q79
7
1 2 3 4 5 6 7
3
12

Assumptions / Restrictions / Clarifications

  • You may assume k >= 1 and n >= 0.
  • The first element (index 0) is always included when n > 0.
  • Walk the array with a pointer that you advance by k each iteration; do not use arr[i] indexing.
  • Do not print anything inside sum_stride.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • pointer arithmetic
  • computing a one-past-the-end pointer
  • loop termination with pointers

Worked example

For example:

For k = 2 over the array 10 20 30 40 50:

  • a pointer starts at index 0 (value 10) and advances by 2 each step.
  • it visits indices 0, 2, 4, reading 10, 30, 50.
  • the sum is 90, so sum_stride returns 90.

Edge cases to consider

  • k == 1 (sum the whole array)
  • k larger than n (only the first element counts)
  • n == 0 (empty, sum 0)
  • a k that lands the pointer exactly on the end

Common mistakes

  • advancing the pointer by 1 instead of k
  • using p <= end and reading one element past the array
  • mixing index arithmetic and pointer arithmetic inconsistently

Optional extension challenge

Add an offset parameter so the walk can start at any index, and support negative strides that walk backwards.

You can re-fetch the starter code for this question: prac_q79.c.

Question 80Column Sums of a Matrix

(4 marks)

Estimated time: 20-30 minutes

A spreadsheet feature needs a totals row that sums each column of a grid of numbers. Write a function that computes the sum of each column of a rows x cols matrix, storing the cols totals into an output array supplied by the caller.

Your task is to complete the function void column_sums(int matrix[10][10], int rows, int cols, int out[10]), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q80.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q80.c:

TERMINAL
gcc -Wall -Wextra -o prac_q80 prac_q80.c
./prac_q80
2 3
1 2 3
4 5 6
5 7 9

./prac_q80
3 2
1 1
2 2
3 3
6 6

./prac_q80
1 4
10 20 30 40
10 20 30 40

Assumptions / Restrictions / Clarifications

  • You may assume rows and cols are each at most 10, matching the fixed array size.
  • out[c] should receive the sum of column c across all rows.
  • Only the first rows x cols region of matrix is meaningful.
  • Do not print anything inside column_sums.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • 2D array traversal
  • iterating column-major
  • writing results into an output array

Worked example

For example:

For the 2x3 matrix [[1,2,3],[4,5,6]]:

  • column 0 sums 1 + 4 = 5.
  • column 1 sums 2 + 5 = 7.
  • column 2 sums 3 + 6 = 9.

The printed totals are "5 7 9".

Edge cases to consider

  • a single-row matrix (totals equal that row)
  • a single-column matrix
  • a 1x1 matrix
  • rows and cols differing (non-square)

Common mistakes

  • swapping the row and column loop bounds
  • accumulating into out[c] without resetting to 0
  • iterating row-major and computing row sums by mistake

Optional extension challenge

Also compute the row sums and the grand total, returning them alongside the column sums.

You can re-fetch the starter code for this question: prac_q80.c.

Question 81Parse Integer With Validation

(4 marks)

Estimated time: 25-35 minutes

A configuration loader must reject fields that are not proper integers instead of silently treating them as zero. This is a defensive-programming exercise. Write a function that parses a string as a signed decimal integer, returning 1 and storing the value via an output pointer if the whole string is a valid integer, or returning 0 (and leaving the output untouched) if it is not.

A valid integer is an optional single leading '-' followed by one or more decimal digits and nothing else. Empty strings, a lone '-', and strings with any non-digit character are invalid.

Your task is to complete the function int parse_int(char *s, int *out), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q81.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q81.c:

TERMINAL
gcc -Wall -Wextra -o prac_q81 prac_q81.c
./prac_q81
-34
-34

./prac_q81
12a
invalid

./prac_q81
007
7

./prac_q81

invalid

./prac_q81
-
invalid

Assumptions / Restrictions / Clarifications

  • The empty string is invalid.
  • A leading '-' is allowed but must be followed by at least one digit.
  • No leading '+', spaces, or trailing characters are permitted.
  • On success, write the parsed value through out and return 1; on failure, return 0 and do not write through out.
  • You may assume any valid value fits in an int.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • string validation / defensive programming
  • manual digit-to-value conversion
  • output parameters with a success flag

Worked example

For example:

For the string "12a":

  • the first two characters '1' and '2' are digits and build the value 12.
  • the character 'a' is not a digit, so the function returns 0 without writing through out.

main prints "invalid".

Edge cases to consider

  • the empty string (invalid)
  • a lone '-' with no digits (invalid)
  • leading zeros like "007" (valid, value 7)
  • a trailing non-digit like "12a"

Common mistakes

  • accepting a '-' with no following digits
  • writing through out even on failure
  • treating the empty string as 0 instead of invalid

Optional extension challenge

Extend it to accept an optional leading '+' and to reject values that would overflow an int.

You can re-fetch the starter code for this question: prac_q81.c.

Question 82Most Frequent Element

(4 marks)

Estimated time: 20-30 minutes

A poll aggregator wants the single most popular choice from a list of votes. Write a function that returns the value that occurs most often in an array of n integers. If two or more values tie for the highest frequency, return the smallest such value.

Your task is to complete the function int most_frequent(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q82.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q82.c:

TERMINAL
gcc -Wall -Wextra -o prac_q82 prac_q82.c
./prac_q82
6
1 2 2 3 3 3
3

./prac_q82
4
4 4 5 5
4

./prac_q82
1
7
7

Assumptions / Restrictions / Clarifications

  • You may assume n >= 1.
  • If several values share the highest frequency, return the smallest of them.
  • Do not modify the input array.
  • Do not print anything inside most_frequent.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • nested-loop frequency counting
  • tracking a best-so-far value
  • deterministic tie-breaking

Worked example

For example:

For the array 4 4 5 5:

  • 4 occurs twice and 5 occurs twice, a tie for the highest frequency.
  • the tie is broken by returning the smaller value, 4.
  • most_frequent therefore returns 4.

Edge cases to consider

  • a single-element array
  • all elements distinct (each frequency 1, smallest wins)
  • all elements equal
  • a tie broken by the smallest value

Common mistakes

  • not defining a tie-break, giving non-deterministic results
  • comparing counts but forgetting to update best_value
  • initialising best_count to a value that the first element cannot beat

Optional extension challenge

Solve it in O(n log n) by sorting a copy first, or discuss how a hash table would make it O(n).

You can re-fetch the starter code for this question: prac_q82.c.

Question 83Titlecase Words

(4 marks)

Estimated time: 20-30 minutes

A headline formatter needs to convert arbitrary text to title case, where the first letter of each word is uppercase and every other letter is lowercase. Write a function that converts a string to title case IN PLACE.

Words are separated by single spaces. Only alphabetic characters are changed; the first alphabetic character of each word becomes uppercase and the rest become lowercase.

Your task is to complete the function void titlecase(char *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q83.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q83.c:

TERMINAL
gcc -Wall -Wextra -o prac_q83 prac_q83.c
./prac_q83
hello world
Hello World

./prac_q83
the QUICK brown FOX
The Quick Brown Fox

./prac_q83
a
A

Assumptions / Restrictions / Clarifications

  • Words are separated by single space characters.
  • The first letter of each word becomes uppercase; all other letters become lowercase.
  • The string is modified in place; do not allocate a new string.
  • You may use functions from <ctype.h> such as toupper and tolower.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • in-place string modification
  • tracking word boundaries
  • toupper/tolower from <ctype.h>

Worked example

For example:

For the string "the QUICK brown FOX":

  • each word's first letter is forced uppercase: 'T', 'Q', 'B', 'F'.
  • every following letter is forced lowercase.
  • the result is "The Quick Brown Fox".

Edge cases to consider

  • the empty string
  • a single-character word
  • already-correct title case
  • all-uppercase input that must be lowered except first letters

Common mistakes

  • only uppercasing and forgetting to lowercase the rest of each word
  • not resetting the start-of-word flag after a space
  • passing a negative char to toupper without the unsigned char cast

Optional extension challenge

Handle multiple spaces and other separators, and keep small words like 'a', 'of', 'the' lowercase unless first.

You can re-fetch the starter code for this question: prac_q83.c.

Question 84Count Distinct Values

(4 marks)

Estimated time: 20-30 minutes

An analytics module wants to know how many unique readings a sensor produced. Write a function that returns the number of distinct values in an array of n integers, without modifying the array.

Your task is to complete the function int count_distinct(int arr[], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q84.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q84.c:

TERMINAL
gcc -Wall -Wextra -o prac_q84 prac_q84.c
./prac_q84
5
1 2 2 3 1
3

./prac_q84
3
5 5 5
1

./prac_q84
0
0

Assumptions / Restrictions / Clarifications

  • If n is 0 the function returns 0.
  • Each value should be counted once no matter how many times it appears.
  • Do not modify or sort the input array.
  • Do not print anything inside count_distinct.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • nested-loop uniqueness check
  • looking back over already-seen elements
  • not mutating the input

Worked example

For example:

For the array 1 2 2 3 1:

  • 1 is new (counts), 2 is new (counts), the second 2 was seen before (skip).
  • 3 is new (counts), the second 1 was seen before (skip).
  • three distinct values remain, so count_distinct returns 3.

Edge cases to consider

  • empty array (0 distinct)
  • all elements equal (1 distinct)
  • all elements distinct (n distinct)
  • negative values

Common mistakes

  • comparing arr[i] against every j, including j >= i, and double-counting
  • sorting the array in place when told not to modify it
  • counting duplicates because the seen-before check is wrong

Optional extension challenge

Return the distinct values themselves into an output array (preserving first-seen order) as well as the count.

You can re-fetch the starter code for this question: prac_q84.c.

Question 85Spiral Fill 2D Array

(5 marks)

Estimated time: 30-45 minutes

A generative art program wants to lay out sequential frame numbers in a spiral pattern across a square grid for a visual effect. Write a function that fills an n x n 2D array with the integers 1 to n * n in spiral order, starting at the top-left corner and spiralling clockwise inward.

The spiral proceeds rightwards along the top row, then downwards along the right column, then leftwards along the bottom row, then upwards along the left column, and continues inward in the same pattern.

Your task is to complete the function void spiral_fill(int grid[10][10], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q85.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q85.c:

TERMINAL
gcc -Wall -Wextra -o prac_q85 prac_q85.c
./prac_q85
3
1 2 3
8 9 4
7 6 5

./prac_q85
1
1

./prac_q85
2
1 2
4 3

Assumptions / Restrictions / Clarifications

  • 1 <= n && n <= 10, matching the fixed array size.
  • Only the first n x n region of grid needs to be written.
  • The spiral always starts at grid[0][0] with the value 1 and proceeds clockwise.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • 2D array indexing
  • managing four moving boundaries
  • layer-by-layer traversal

Worked example

For example:

For n = 3:

  • the top row is filled left to right: 1 2 3.
  • the right column (excluding the corner already filled) is filled top to bottom: 4 5.
  • the bottom row is filled right to left: 6 7.
  • the left column is filled bottom to top: 8.
  • only the centre cell (9) remains and is filled last.

The resulting grid is [[1,2,3],[8,9,4],[7,6,5]].

Edge cases to consider

  • a 1x1 grid
  • an even vs odd dimension where the centre is handled last
  • a larger grid filling all n*n values

Common mistakes

  • overwriting the centre cell twice, or missing it entirely
  • off-by-one on the shrinking top/bottom/left/right bounds
  • moving in the wrong order (should be right, down, left, up)

Optional extension challenge

Fill the spiral in the opposite (counter-clockwise) direction, or start the spiral from the centre outwards.

You can re-fetch the starter code for this question: prac_q85.c.

Question 86Find All Pairs Summing to Target

(5 marks)

Estimated time: 20-30 minutes

A puzzle game challenges players to find how many distinct pairs of cards, drawn from a hand of uniquely numbered cards, sum to a target value shown on the screen. Write a function that, given an array of n distinct integers and a target sum, counts the number of unordered pairs of distinct elements in the array whose values add up to the target.

Each unordered pair should be counted exactly once, so the pair (1, 5) and the pair (5, 1) are the same pair and must not both be counted.

Your task is to complete the function int count_pairs_with_sum(int arr[], int n, int target), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q86.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q86.c:

TERMINAL
gcc -Wall -Wextra -o prac_q86 prac_q86.c
./prac_q86
5
1 2 3 4 5
6
2

./prac_q86
5
1 2 3 4 5
100
0

./prac_q86
1
3
6
0

Assumptions / Restrictions / Clarifications

  • All values in arr are distinct (no duplicate values).
  • A pair (arr[i], arr[j]) with i != j counts once, regardless of order.
  • An element cannot be paired with itself, even if target is exactly double that element's value.
  • n may be 0 or 1, in which case there can be no pairs, and the function should return 0.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • nested loops over index pairs
  • avoiding double counting
  • counting rather than printing

Worked example

For example:

For arr = [1, 2, 3, 4, 5] and target = 6:

  • the pair (1, 5) sums to 6 -- counts.
  • the pair (2, 4) sums to 6 -- counts.
  • every other unordered pair (1,2),(1,3),(1,4),(2,3),(2,5),(3,4),(3,5),(4,5) does not sum to 6.

count_pairs_with_sum returns 2.

Edge cases to consider

  • an empty or single-element array (no pairs)
  • a value that pairs with itself (i and j must differ)
  • a target that cannot be formed by any pair
  • multiple distinct pairs achieving the same sum

Common mistakes

  • counting the pair (i, j) and (j, i) as two distinct pairs
  • pairing an element with itself (i == j)
  • off-by-one starting the inner loop at i instead of i+1

Optional extension challenge

Return the pairs themselves into an output 2D array instead of just returning the count.

You can re-fetch the starter code for this question: prac_q86.c.

Question 87Josephus Survivor (Array Simulation)

(5 marks)

Estimated time: 30-45 minutes

A party game inspired by the classic Josephus problem eliminates players in a circle one by one until a single winner remains, and the organiser wants to be able to predict who will win before playing. n people numbered 0 to n - 1 stand in a circle. Starting at person 0 and counting around the circle, every kth remaining person is eliminated, until only one person remains.

Write a function that simulates this process using an array (marking eliminated people, or shrinking the array) and returns the number of the last remaining person.

Your task is to complete the function int josephus_survivor(int n, int k), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q87.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q87.c:

TERMINAL
gcc -Wall -Wextra -o prac_q87 prac_q87.c
./prac_q87
7 3
3

./prac_q87
1 5
0

./prac_q87
4 1
3

Assumptions / Restrictions / Clarifications

  • n >= 1, k >= 1.
  • If n == 1, the sole person (0) survives immediately, with no eliminations.
  • Counting wraps around the circle, skipping over people who have already been eliminated.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • circular array simulation
  • marking elements as eliminated
  • modular index stepping

Worked example

For example:

For n = 4, k = 1 (every 1st remaining person is eliminated):

  • starting at person 0: person 0 is eliminated first, then 1, then 2, in order.
  • only person 3 is left.

josephus_survivor returns 3.

Edge cases to consider

  • a single person (they survive)
  • a step count k of 1 (eliminate in order)
  • k larger than the number of people (wrap around)
  • two people

Common mistakes

  • counting eliminated people when stepping k positions
  • off-by-one so the wrong person is eliminated each round
  • an infinite loop when the last survivor is not detected

Optional extension challenge

Return the full elimination order, not just the final survivor, and compare against the closed-form recurrence.

You can re-fetch the starter code for this question: prac_q87.c.

Question 88Validate Sudoku Row/Column

(5 marks)

Estimated time: 30-40 minutes

A Sudoku helper app wants to give players live feedback about whether the row or column they just edited still obeys the game's rules. Write a function that checks whether a given row (or column) of a 9x9 Sudoku grid, passed as an array of 9 integers each between 1 and 9 inclusive with 0 representing an empty cell, contains any duplicate non-zero values.

Return 1 if the row is valid (no duplicate non-zero values), 0 otherwise. The same function can be used to validate either a row or a column, since both are just passed in as an array of 9 integers.

Your task is to complete the function int is_valid_line(int line[9]), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q88.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q88.c:

TERMINAL
gcc -Wall -Wextra -o prac_q88 prac_q88.c
./prac_q88
5 3 0 0 7 0 0 0 0
1

./prac_q88
5 3 0 0 7 0 0 0 5
0

./prac_q88
5 3 4 6 7 8 9 1 2
1

Assumptions / Restrictions / Clarifications

  • Each element of line is between 0 and 9 inclusive.
  • 0 means empty and never counts as a duplicate, so a line may contain any number of 0s without being invalid.
  • line always has exactly 9 elements, matching a Sudoku row or column.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • scanning a line for duplicates
  • a seen[] presence table
  • distinguishing filled from empty cells

Worked example

For example:

For line = [5, 3, 0, 0, 7, 0, 0, 0, 5]:

  • 5 is seen for the first time at index 0 -- mark it seen.
  • 3 and 7 are each seen once, no conflicts.
  • the 0s are ignored throughout.
  • at index 8, 5 is seen again, which is a duplicate non-zero value.

is_valid_line returns 0.

Edge cases to consider

  • a completely empty line (no filled cells) which is valid
  • a line with a single duplicate pair
  • a fully filled valid line 1..9
  • handling the 0 (blank) marker correctly

Common mistakes

  • treating repeated blank/0 cells as duplicates
  • indexing the seen[] table with the digit without adjusting for 1-based values
  • checking only rows or only columns when both are required

Optional extension challenge

Extend the check to the nine 3x3 boxes as well, validating a whole board rather than a single line.

You can re-fetch the starter code for this question: prac_q88.c.

Question 89Word Frequency Counter

(5 marks)

Estimated time: 30-40 minutes

A simple text-mining tool has already split a document into an array of individual lowercase words and wants to know how often a particular word of interest appears. Given an array of n lowercase words (each a null-terminated string, no spaces) and a target word, write a function that counts how many times the target word appears in the array (exact match).

Matching must be an exact, whole-word comparison; partial matches (e.g. "cats" matching a search for "cat") should not be counted.

Your task is to complete the function int word_frequency(char *words[], int n, char *target), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q89.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q89.c:

TERMINAL
gcc -Wall -Wextra -o prac_q89 prac_q89.c
./prac_q89
5
cat dog cat bird cat
cat
3

./prac_q89
5
cat dog cat bird cat
fish
0

./prac_q89
0
cat
0

Assumptions / Restrictions / Clarifications

  • n may be 0, in which case the function should return 0.
  • Comparison is exact and case-sensitive.
  • target is always a valid, non-NULL, null-terminated string.
  • Words in the array may repeat any number of times.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • arrays of strings
  • comparing words with strcmp
  • counting occurrences of a query

Worked example

For example:

For words = ["cat", "dog", "cat", "bird", "cat"] and target = "cat":

  • words[0] "cat" matches exactly -- counts.
  • words[1] "dog" does not match.
  • words[2] "cat" matches -- counts.
  • words[3] "bird" does not match.
  • words[4] "cat" matches -- counts.

word_frequency returns 3.

Edge cases to consider

  • an empty document (no words)
  • a query word that does not occur
  • case differences between the query and the words
  • the query occurring as a substring but not a whole word

Common mistakes

  • using == to compare strings instead of strcmp
  • matching substrings rather than whole words
  • off-by-one over the array of words

Optional extension challenge

Return the most frequent word in the document rather than counting a single supplied query word.

You can re-fetch the starter code for this question: prac_q89.c.

Question 90Linked List: Find Middle Node

(5 marks)

Estimated time: 25-35 minutes

Note prac_q90.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A media player wants to jump straight to roughly the middle of a playlist stored as a singly linked list, without first walking the whole list to count how many songs it contains. A singly linked list of integers is defined by the provided node struct.

Write a function that returns a pointer to the middle node of the list. If the list has an even number of nodes, return the second of the two middle nodes. You should do this in a single traversal using two pointers moving at different speeds (the classic 'slow/fast pointer' technique), not by first counting the length.

Your task is to complete the function struct node *find_middle(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q90.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q90.c:

TERMINAL
gcc -Wall -Wextra -o prac_q90 prac_q90.c
./prac_q90
5
1 2 3 4 5
3

./prac_q90
4
1 2 3 4
3

./prac_q90
1
9
9

Assumptions / Restrictions / Clarifications

  • The list is never empty when this function is called.
  • You must use the slow/fast pointer technique in a single pass; counting the length first and then traversing again is not an acceptable solution.
  • For an even-length list, the second of the two middle nodes is returned.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • slow/fast pointer technique
  • single-pass traversal
  • even vs odd length handling

Worked example

For example:

For the list 1 -> 2 -> 3 -> 4 -> NULL (even length):

  • slow and fast both start at node 1.
  • step 1: slow moves to 2, fast moves to 3.
  • step 2: slow moves to 3, fast moves to NULL (since fast->next->next runs off the end), so the loop stops.

find_middle returns the node containing 3, the second of the two middle nodes (2 and 3).

Edge cases to consider

  • a single-node list (that node is the middle)
  • an even-length list (the second middle node is returned)
  • a two-node list
  • an odd-length list

Common mistakes

  • advancing the fast pointer without a NULL check and dereferencing NULL
  • returning the wrong middle for even-length lists
  • using length / 2 with an off-by-one after counting nodes

Optional extension challenge

Return both middle nodes for an even-length list, or the exact middle for odd.

You can re-fetch the starter code for this question: prac_q90.c.

Question 91Validate Date

(5 marks)

Estimated time: 25-35 minutes

A booking form must reject impossible dates before they reach the database. This is a defensive-programming exercise: write a function that returns 1 if the given day, month and year form a valid calendar date and 0 otherwise, rejecting every malformed combination.

A valid date has a month from 1 to 12, a positive year, and a day from 1 to the number of days in that month, accounting for leap years in February.

Your task is to complete the function int is_valid_date(int day, int month, int year), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q91.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q91.c:

TERMINAL
gcc -Wall -Wextra -o prac_q91 prac_q91.c
./prac_q91
29 2 2024
1

./prac_q91
29 2 2023
0

./prac_q91
31 4 2021
0

./prac_q91
15 13 2020
0

./prac_q91
1 1 1
1

Assumptions / Restrictions / Clarifications

  • Year must be at least 1.
  • Month must be in the range 1 to 12.
  • Day must be at least 1 and at most the number of days in the given month.
  • February has 29 days in a leap year and 28 otherwise; use the standard leap-year rule.
  • Do not print anything inside is_valid_date.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • input validation / defensive programming
  • lookup tables for month lengths
  • combining several guard conditions

Worked example

For example:

For 29/2/2023:

  • the month 2 is in range and the year is positive.
  • 2023 is not a leap year, so February has only 28 days.
  • day 29 exceeds 28, so is_valid_date returns 0.

Edge cases to consider

  • 29 February in leap and non-leap years
  • the 31st day in a 30-day month
  • a month number of 0 or 13 (out of range)
  • day zero or a negative year

Common mistakes

  • forgetting the leap-year adjustment for February
  • indexing the days array with month instead of month - 1
  • validating only some fields and letting others through

Optional extension challenge

Return its ordinal position within the year (1-366) for a valid date, or -1 for an invalid one, in the same function family.

You can re-fetch the starter code for this question: prac_q91.c.

Question 92Insert Into Sorted Linked List

(5 marks)

Estimated time: 25-35 minutes

Note prac_q92.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A priority mailbox keeps its messages in ascending order of a numeric key. Write a function that inserts a new value into an already-sorted singly linked list so that the list remains sorted in ascending order, returning a pointer to the (possibly new) head of the list.

Your task is to complete the function struct node *insert_sorted(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q92.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q92.c:

TERMINAL
gcc -Wall -Wextra -o prac_q92 prac_q92.c
./prac_q92
4
1 3 5 7
4
1 3 4 5 7

./prac_q92
3
1 2 3
0
0 1 2 3

./prac_q92
2
1 2
9
1 2 9

Assumptions / Restrictions / Clarifications

  • The input list is already sorted in ascending order.
  • You must allocate exactly one new node with malloc for the inserted value.
  • If the new value is smaller than every existing value, it becomes the new head.
  • Duplicate values are allowed; place the new node before or after equal values (either is accepted).
  • Return the head of the resulting list.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • linked list insertion
  • malloc for a single node
  • splicing pointers without losing the list

Worked example

For example:

For inserting 4 into the sorted list 1 3 5 7:

  • 4 is not smaller than the head (1), so we walk forward.
  • we stop when the next value (5) is not less than 4, i.e. after the node 3.
  • the new node is spliced between 3 and 5, giving 1 3 4 5 7.

Edge cases to consider

  • inserting into an empty list (new node becomes head)
  • a value smaller than the head
  • a value larger than every element (append at the tail)
  • duplicate values

Common mistakes

  • forgetting to return the new head when inserting at the front
  • linking new_node->next after already overwriting curr->next
  • not allocating a node, or leaking it on an early return

Optional extension challenge

Add a delete_sorted function that removes the first node with a given value while keeping the list sorted.

You can re-fetch the starter code for this question: prac_q92.c.

Question 93Delete First Node With Value

(5 marks)

Estimated time: 25-35 minutes

Note prac_q93.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A task manager stores its jobs in a singly linked list and needs to cancel a job by value. Write a function that deletes the FIRST node whose value equals the given target, frees that node, and returns a pointer to the (possibly new) head of the list.

If no node has the target value, the list is returned unchanged.

Your task is to complete the function struct node *delete_value(struct node *head, int value), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q93.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q93.c:

TERMINAL
gcc -Wall -Wextra -o prac_q93 prac_q93.c
./prac_q93
5
1 2 3 4 3
3
1 2 4 3

./prac_q93
3
1 2 3
1
2 3

./prac_q93
3
1 2 3
9
1 2 3

Assumptions / Restrictions / Clarifications

  • Only the first matching node should be removed, even if several nodes share the value.
  • The removed node must be freed with free to avoid a memory leak.
  • If the target is not present, return the list unchanged.
  • If the head node is the one removed, return the new head.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • linked list deletion
  • freeing an unlinked node
  • handling head-vs-interior removal

Worked example

For example:

For deleting value 3 from the list 1 2 3 4 3:

  • the head (1) does not match, so we walk forward looking at each next node.
  • the first node whose value is 3 is found; it is unlinked and freed.
  • only that first 3 is removed, leaving 1 2 4 3.

Edge cases to consider

  • deleting the head node
  • the value not being present (list unchanged)
  • an empty list
  • several nodes sharing the value (only the first goes)

Common mistakes

  • freeing the node before saving its next pointer
  • forgetting to free the removed node (a leak)
  • removing every matching node instead of only the first

Optional extension challenge

Write delete_all_values so it removes every node with the target value in a single traversal.

You can re-fetch the starter code for this question: prac_q93.c.

Question 94Dynamic 2D Array Builder

(6 marks)

Estimated time: 30-45 minutes

A spreadsheet-like application needs to support grids of arbitrary size chosen at runtime, which rules out using a fixed-size 2D array. Write a function that dynamically allocates a rows x cols 2D array of integers on the heap (an array of rows pointers, each pointing to a dynamically allocated array of cols ints), initialises every element to 0, and returns a pointer to the array of row pointers.

Also write the matching free function that releases all memory allocated by the builder, given the same rows value used to build it, so that no dynamically allocated grid ever leaks memory.

Your task is to complete the functions int **make_grid(int rows, int cols) and void free_grid(int **grid, int rows), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q94.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q94.c:

TERMINAL
gcc -Wall -Wextra -o prac_q94 prac_q94.c
./prac_q94
3 4
0 0 0 0
0 0 0 0
0 0 0 0

./prac_q94
3 4
1 2 9
0 0 0 0
0 0 9 0
0 0 0 0

./prac_q94
1 1
0

Assumptions / Restrictions / Clarifications

  • rows and cols are each greater than 0.
  • free_grid must not leak any memory and must not double free.
  • You must use malloc/calloc; do not use a fixed-size 2D array.
  • Every element of the newly created grid must be initialised to 0 before make_grid returns.
  • free_grid is always called with the same rows value that was originally passed to make_grid.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • malloc for an array of row pointers
  • allocating each row
  • freeing in the reverse order and avoiding leaks

Worked example

For example:

For rows = 3, cols = 4, with cell (1, 2) set to 9:

  • make_grid allocates 3 row pointers, each pointing to a calloc'd array of 4 ints (all zero).
  • grid[1][2] is then set to 9 by main.
  • every other cell remains 0, since calloc zero-initialises memory.

The printed grid is three rows: "0 0 0 0", "0 0 9 0", "0 0 0 0".

Edge cases to consider

  • a 1x1 single-cell grid
  • a non-square (rows != cols) grid
  • a single-row or single-column allocation
  • a grid with larger dimensions

Common mistakes

  • freeing the outer pointer before freeing each row (leaking the rows)
  • not checking malloc for NULL before writing
  • confusing rows and columns when indexing grid[r][c]

Optional extension challenge

Allocate a 3D grid int ***make_3d(int d, int r, int c) and free it cleanly.

You can re-fetch the starter code for this question: prac_q94.c.

Question 95Linked List: Reverse In Place

(6 marks)

Estimated time: 45-60 minutes

Note prac_q95.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A music player's recently-played history is stored as a singly linked list with the most recently added song at the head; the developers now want to be able to flip it around to show the oldest entries first, without using any extra memory for a second list. A singly linked list of integers is defined by the provided node struct. Write a function that reverses the list in place (rearranging the existing nodes' next pointers, without allocating any new nodes) and returns a pointer to the new head of the reversed list.

Because the reversal happens in place, after the call the original head node becomes the new tail of the list, and its next pointer must be updated accordingly.

Your task is to complete the function struct node *reverse_list(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q95.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q95.c:

TERMINAL
gcc -Wall -Wextra -o prac_q95 prac_q95.c
./prac_q95
3
1 2 3
3 2 1

./prac_q95
1
5
5

./prac_q95
0

Assumptions / Restrictions / Clarifications

  • The list may be empty (head is NULL), in which case return NULL.
  • You must not allocate any new nodes; only rewire existing next pointers.
  • You may not use any array or other auxiliary linked list to store nodes.
  • A list with a single node should be returned unchanged (still pointing to itself, next NULL).
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • pointer rewiring
  • the prev/curr/next three-pointer pattern
  • returning the new head

Worked example

For example:

For the list 1 -> 2 -> 3 -> NULL:

  • prev = NULL, curr = 1. Save next (2), point 1's next at prev (NULL), advance: prev = 1, curr = 2.
  • save next (3), point 2's next at prev (1), advance: prev = 2, curr = 3.
  • save next (NULL), point 3's next at prev (2), advance: prev = 3, curr = NULL; the loop ends.

reverse_list returns prev, which is node 3, now heading the list 3 -> 2 -> 1 -> NULL.

Edge cases to consider

  • an empty list (returns NULL)
  • a single-node list (unchanged)
  • a two-node list (the minimal real reversal)
  • ensuring the old head's next becomes NULL

Common mistakes

  • losing the rest of the list by overwriting next before saving it
  • returning the old head instead of the new one
  • creating a cycle by not terminating the tail with NULL

Optional extension challenge

Reverse only the sub-list between the m-th and n-th nodes, leaving the rest of the list intact.

You can re-fetch the starter code for this question: prac_q95.c.

Question 96Debug and Fix: Linked List Length

(6 marks)

Estimated time: 30-45 minutes

Note prac_q96.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

The starter file contains a function that is meant to return the number of nodes in a singly linked list, but it is broken in two ways: it crashes on an empty list, and even on a non-empty list it returns a count that is off by one. Your task is to find and fix both bugs.

This is a debugging exercise. Trace through the given loop carefully for a one-node list and for an empty list to see exactly where it goes wrong, then repair it so it counts every node and safely handles NULL.

Your task is to complete the function int list_length(struct node *head), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q96.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q96.c:

TERMINAL
gcc -Wall -Wextra -o prac_q96 prac_q96.c
./prac_q96
3
10 20 30
3

./prac_q96
1
5
1

./prac_q96
0
0

Assumptions / Restrictions / Clarifications

  • After your fix, the length of an empty (NULL) list must be 0.
  • The corrected function must count every node exactly once.
  • Do not change the prototype or the main function.
  • Do not free or modify the list inside list_length.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • debugging linked-list traversal
  • the difference between curr and curr->next as a loop guard
  • NULL safety

Worked example

For example:

For the list 10 20 30 (after fixing):

  • curr walks 10 -> 20 -> 30 -> NULL, incrementing count on each real node.
  • the loop stops when curr is NULL, having counted 3 nodes.

The unfixed starter loops while curr->next is non-NULL, so it stops one node early (returning 2) and dereferences NULL on an empty list.

Edge cases to consider

  • an empty list, which must return 0 not crash
  • a single-node list (must return 1)
  • a long list
  • confirming the last node is counted

Common mistakes

  • looping on curr->next instead of curr
  • dereferencing head without a NULL check
  • fixing the crash but leaving the off-by-one (or vice versa)

Optional extension challenge

Add a recursive version and compare how each behaves on a very long list with respect to stack usage.

You can re-fetch the starter code for this question: prac_q96.c.

Question 97File-Based Student Records Loader

(7 marks)

Estimated time: 50-70 minutes

Note prac_q97.c uses the following data type:

C
struct student {
    char name[50];
    int mark;
};

An administrative tool needs to import student results from a comma-separated text file exported by another system, without knowing in advance how many students the file contains. Records of students are stored one per line in a text file, each line formatted as name,mark (e.g. Grace,95), with no spaces around the comma.

Write a function that opens a file with the given filename, reads all the records from it into a dynamically allocated array of struct student (defined in the provided starter header), and returns a pointer to that array via an output parameter, also writing the number of records read via a second output parameter.

Your task is to complete the function void load_students(char *filename, struct student **out_arr, int *out_count), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q97.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q97.c:

TERMINAL
gcc -Wall -Wextra -o prac_q97 prac_q97.c
./prac_q97
students.csv
4 students loaded
arr[1]: Grace 95

./prac_q97
missing.csv
0 students loaded

./prac_q97
solo.csv
1 students loaded
arr[0]: Katherine 91

Assumptions / Restrictions / Clarifications

  • If the file cannot be opened, set *out_arr to NULL and *out_count to 0.
  • Each name is at most 49 characters (fits in the student struct's name field).
  • The caller is responsible for freeing the returned array.
  • The file may contain any number of records, including zero (an empty file).
  • Lines are always well-formed as name,mark with no surrounding whitespace.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • opening a file with fopen
  • parsing lines with fgets/fscanf
  • checking for read failure and closing the file

Worked example

For example:

For students.csv containing "Ada,88\nGrace,95\nAlan,95\nKatherine,91\n":

  • fopen succeeds, so the file is read line by line.
  • each line is parsed as name,mark with fscanf, appending a new struct student to the dynamically-growing array.
  • 4 records are read in total: Ada 88, Grace 95, Alan 95, Katherine 91.

main prints "4 students loaded" and then "arr[1]: Grace 95", since arr[1] is the second record read.

Edge cases to consider

  • a file that does not exist (fopen returns NULL)
  • an empty file (zero records)
  • a trailing newline or blank final line
  • more records than the array can hold

Common mistakes

  • not checking the return value of fopen before reading
  • forgetting to fclose, leaking the file handle
  • assuming a fixed number of lines instead of looping until EOF

Optional extension challenge

Compute and print class statistics (min, max, mean mark) as you load, and skip malformed lines gracefully.

You can re-fetch the starter code for this question: prac_q97.c.

Question 98Binary Search Tree Insert and Height

(7 marks)

Estimated time: 35-50 minutes

Note prac_q98.c uses the following data type:

C
struct tree_node {
    int value;
    struct tree_node *left;
    struct tree_node *right;
};

A dictionary application wants to store a growing set of unique integer keys such that they stay easy to search and can also report how unbalanced the resulting structure has become. A binary search tree (BST) of integers is defined by the provided struct tree_node. Write two functions: one that inserts a new value into the BST (allocating a new node as needed) and returns the (possibly new) root of the tree, and one that computes the height of a tree.

The height of a tree is the number of nodes on the longest path from root to a leaf; an empty tree has height 0, and a tree with a single node has height 1.

Your task is to complete the functions struct tree_node *bst_insert(struct tree_node *root, int value) and int tree_height(struct tree_node *root), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q98.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q98.c:

TERMINAL
gcc -Wall -Wextra -o prac_q98 prac_q98.c
./prac_q98
4
5 3 8 1
3

./prac_q98
0
0

./prac_q98
2
5 5
1

Assumptions / Restrictions / Clarifications

  • Duplicate values should not be inserted (leave the tree unchanged if value already exists).
  • bst_insert must maintain the BST property: left subtree values < node value < right subtree values.
  • The tree may be empty (root is NULL) at the start.
  • bst_insert must always have its result assigned back to the root variable, since the root may change when the tree was previously empty.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • recursive BST insertion
  • maintaining the ordering invariant
  • computing height recursively

Worked example

For example:

For inserting 5, 3, 8, 1 in order:

  • 5 becomes the root (empty tree).
  • 3 < 5, goes to the left of 5.
  • 8 > 5, goes to the right of 5.
  • 1 < 5, then 1 < 3, goes to the left of 3.

The tree is: 5 with left child 3 (which has left child 1) and right child 8.

The longest root-to-leaf path is 5 -> 3 -> 1, which has 3 nodes, so tree_height returns 3.

Edge cases to consider

  • inserting into an empty tree
  • inserting a duplicate value
  • a degenerate tree from already-sorted inserts
  • a single-node tree (height 1)

Common mistakes

  • leaking nodes or losing the tree by not returning the (possibly new) root
  • off-by-one in the height base cases
  • placing values on the wrong side of a node

Optional extension challenge

Add a function int bst_is_balanced(struct tree_node *root) that checks whether the tree is AVL-balanced.

You can re-fetch the starter code for this question: prac_q98.c.

Question 99Custom Dynamic String Buffer (ADT)

(7 marks)

Estimated time: 50-70 minutes

Note prac_q99.c uses the following data type:

C
struct strbuf {
    char *data;
    int length;
    int capacity;
};

A logging library needs to build up long log messages piece by piece (timestamps, tags, messages) without knowing the final length in advance, so a fixed-size char array is not suitable. Implement a simple growable string buffer ADT, struct strbuf (see the provided starter header), which stores a heap-allocated, null-terminated character array plus its current length and allocated capacity.

Write a function that creates a new, empty buffer, a function that appends a null-terminated C string onto the end of the buffer (growing the underlying allocation with realloc as needed, doubling capacity when full), and a function that frees all memory owned by the buffer.

Your task is to complete the functions struct strbuf strbuf_new(void), void strbuf_append(struct strbuf *buf, char *s) and void strbuf_free(struct strbuf *buf), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q99.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q99.c:

TERMINAL
gcc -Wall -Wextra -o prac_q99 prac_q99.c
./prac_q99
2
Hello,
 world!
Hello, world!
13

./prac_q99
1

0

./prac_q99
1
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
96

Assumptions / Restrictions / Clarifications

  • A newly created buffer represents the empty string (length 0).
  • strbuf_append may be called any number of times and must always keep the buffer's data null-terminated.
  • strbuf_append must correctly grow the underlying allocation (via realloc, doubling capacity) whenever there is not enough room for the appended text.
  • strbuf_free must not leak memory, and after being called, buf->data should be set to NULL.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • realloc-based growable buffers
  • amortised doubling of capacity
  • keeping length and capacity in sync

Worked example

For example:

For appending "Hello," then " world!":

  • strbuf_new creates an empty buffer (length 0, capacity 0).
  • appending "Hello," (6 characters) needs 7 bytes including the terminator, more than capacity 0, so the buffer grows to capacity 8; data becomes "Hello,", length becomes 6.
  • appending " world!" (7 characters) needs 6+7+1=14 bytes, more than capacity 8, so the buffer grows (doubling to 16); data becomes "Hello, world!", length becomes 13.

main prints "Hello, world!" then "13".

Edge cases to consider

  • appending to a freshly created empty buffer
  • appending a string longer than the current spare capacity
  • appending the empty string
  • many small appends forcing repeated growth

Common mistakes

  • forgetting to null-terminate after each append
  • using the old pointer after realloc moves the block
  • confusing length (used) with capacity (allocated) and overflowing

Optional extension challenge

Add insert-at-index and delete-range operations that shift the buffer contents correctly.

You can re-fetch the starter code for this question: prac_q99.c.

Question 100Undo Stack for a Text Editor

(7 marks)

Estimated time: 50-70 minutes

Note prac_q100.c uses the following data type:

C
struct edit_node {
    char text[200];
    struct edit_node *next;
};

A minimal editor wants to support an unlimited number of undo steps for a single line as the user edits it. This undo system stores previous versions of the line on a stack implemented as a singly linked list, using the provided edit_node struct, where the head of the list is the most recent version.

Write a function that pushes a new version of the line onto the stack (allocating a new node that copies the given string), and a function that pops and discards the most recent version, returning the contents of the version that becomes current (i.e. the new head) via a buffer supplied by the caller, or writing the empty string if the stack becomes empty.

Your task is to complete the functions struct edit_node *undo_push(struct edit_node *head, char *text) and struct edit_node *undo_pop(struct edit_node *head, char *current_out), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q100.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q100.c:

TERMINAL
gcc -Wall -Wextra -o prac_q100 prac_q100.c
./prac_q100
2
hello
hello world
cur is now: hello

./prac_q100
1
only version
cur is now: 

./prac_q100
3
v1
v2
v3
cur is now: v2

Assumptions / Restrictions / Clarifications

  • Strings passed to undo_push are at most 199 characters long.
  • undo_pop must free the popped node.
  • current_out points to a buffer at least 200 characters long.
  • If undo_pop empties the stack completely, current_out should be set to the empty string and the function should return NULL.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • stack ADT via a linked list
  • push/pop discipline
  • freeing popped nodes

Worked example

For example:

For pushing "v1", then "v2", then "v3":

  • after all pushes, the stack (head to tail) is v3 -> v2 -> v1 -> NULL.
  • undo_pop frees the head node (v3) and returns the new head, which is v2.
  • current_out is set to the contents of the new head, "v2".

main prints "cur is now: v2".

Edge cases to consider

  • popping from an empty stack
  • a single push then pop
  • interleaved pushes and pops
  • clearing the whole stack and freeing every node

Common mistakes

  • memory-leaking popped nodes by not freeing them
  • returning a value from an empty stack without signalling underflow
  • losing the rest of the stack when unlinking the top node

Optional extension challenge

Add a redo stack so that popped (undone) actions can be re-applied until a new action clears the redo history.

You can re-fetch the starter code for this question: prac_q100.c.

Question 101Merge Two Sorted Linked Lists

(7 marks)

Estimated time: 45-60 minutes

Note prac_q101.c uses the following data types:

struct node, a singly linked list node with an int value and a struct node *next (with helper functions build_list, print_list and free_list already provided for you in node.h)

A merge step in an external sort needs to combine two already-sorted runs held as linked lists into a single sorted list. Write a function that merges two ascending singly linked lists into one ascending list, reusing the existing nodes (not allocating new ones), and returns the head of the merged list.

Your task is to complete the function struct node *merge_sorted(struct node *a, struct node *b), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q101.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q101.c:

TERMINAL
gcc -Wall -Wextra -o prac_q101 prac_q101.c
./prac_q101
3
1 3 5
3
2 4 6
1 2 3 4 5 6

./prac_q101
0
3
1 2 3
1 2 3

./prac_q101
2
1 2
0
1 2

Assumptions / Restrictions / Clarifications

  • Both input lists are already sorted in ascending order.
  • Reuse the existing nodes by relinking them; do not allocate new nodes.
  • Either or both input lists may be empty (NULL).
  • The merged list must contain every node from both inputs, in ascending order.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • merging two linked lists
  • the dummy-head technique
  • relinking nodes without allocation

Worked example

For example:

For list A = 1 3 5 and list B = 2 4 6:

  • the heads 1 and 2 are compared; 1 is smaller and is linked first.
  • then 3 vs 2 links 2, then 3 vs 4 links 3, and so on.
  • when one list runs out, the remainder of the other is attached, giving 1 2 3 4 5 6.

Edge cases to consider

  • one list empty and the other not
  • both lists empty (result NULL)
  • lists with equal values across both inputs
  • all of one list smaller than the other

Common mistakes

  • allocating new nodes and leaking or duplicating the originals
  • forgetting to attach the leftover tail of the longer list
  • losing the head by not using a dummy node or saving the first result

Optional extension challenge

Merge k sorted lists (given an array of heads) efficiently, for example by repeatedly merging pairs.

You can re-fetch the starter code for this question: prac_q101.c.

Question 102Count Words in a File

(7 marks)

Estimated time: 40-55 minutes

A word-count utility (like the Unix wc -w) needs to count the words in a text file. Write a function that opens the file with the given name, counts the number of whitespace-separated words in it, and returns that count.

A word is any maximal run of non-whitespace characters. Words may be separated by spaces, tabs or newlines, and there may be leading or trailing whitespace.

Your task is to complete the function int count_words_in_file(char *filename), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q102.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q102.c:

TERMINAL
gcc -Wall -Wextra -o prac_q102 prac_q102.c
./prac_q102
poem.txt
9

./prac_q102
empty.txt
0

./prac_q102
missing.txt
-1

Assumptions / Restrictions / Clarifications

  • If the file cannot be opened, return -1.
  • Whitespace includes spaces, tabs and newlines.
  • Runs of multiple whitespace characters separate a single pair of words (no empty words).
  • Remember to close the file before returning.
  • An empty file contains 0 words.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • file handling with fopen/fgetc/fclose
  • checking for open failure
  • word-boundary state tracking

Worked example

For example:

For poem.txt containing "the quick brown fox\njumps over the lazy dog\n":

  • the function reads character by character, starting a new word on each transition from whitespace to non-whitespace.
  • the words are: the, quick, brown, fox, jumps, over, the, lazy, dog.
  • that is 9 words, so count_words_in_file returns 9.

Edge cases to consider

  • a file that cannot be opened (return -1)
  • an empty file (0 words)
  • leading/trailing whitespace and blank lines
  • words separated by tabs or multiple spaces

Common mistakes

  • not checking fopen for NULL before reading
  • forgetting to fclose the file (leaking the handle)
  • counting whitespace runs as extra words

Optional extension challenge

Also return the line count and character count via output parameters, reproducing all three wc figures.

You can re-fetch the starter code for this question: prac_q102.c.

Question 103Command-Line RPN Calculator

(7 marks)

Estimated time: 45-60 minutes

A scripting tool evaluates small arithmetic expressions entered as a line of whitespace-separated tokens in Reverse Polish Notation (RPN, also called postfix). In RPN, operators come after their operands, so 3 4 + means 3 + 4 and 5 1 2 + 4 * + means 5 + ((1 + 2) * 4). Write a function that evaluates such an expression given as an array of string tokens and returns the result.

Each token is either an integer literal or one of the four operators +, -, * and /. Evaluate the expression using an explicit stack: push numbers, and on an operator pop the top two values, apply it, and push the result.

Your task is to complete the function long evaluate_rpn(int count, char *tokens[]), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q103.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q103.c:

TERMINAL
gcc -Wall -Wextra -o prac_q103 prac_q103.c
./prac_q103
3 4 +
7

./prac_q103
5 1 2 + 4 * + 3 -
14

./prac_q103
2 3 4 * +
14

Assumptions / Restrictions / Clarifications

  • count is the number of tokens; tokens[0..count-1] are the token strings.
  • The four supported operators are +, -, * and /, all using integer arithmetic.
  • For a binary operator, the second-popped value is the left operand: for tokens a b - the result is a - b.
  • You may assume the expression is well-formed and never divides by zero.
  • Return the single value left on the stack.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • command-line argument handling
  • an explicit array-based stack
  • postfix (RPN) evaluation and operator dispatch

Worked example

For example:

For the tokens 5 1 2 + 4 * + 3 -:

  • push 5, 1, 2; + pops 1 and 2 and pushes 3.
  • push 4; * pops 3 and 4 and pushes 12; + pops 5 and 12 and pushes 17.
  • push 3; - pops 17 and 3 and pushes 14.
  • the final value on the stack is 14.

Edge cases to consider

  • a single number with no operators
  • operand order for non-commutative - and /
  • negative results
  • distinguishing the operator - from a negative-number token (single-char check)

Common mistakes

  • popping the operands in the wrong order for - and /
  • treating a multi-character token like -5 as the minus operator
  • reading the result from the wrong stack position

Optional extension challenge

Support unary minus and a modulo operator, and detect malformed expressions (too few operands) instead of assuming well-formed input.

You can re-fetch the starter code for this question: prac_q103.c.

Question 104Generic Dynamic Stack (typedef ADT)

(7 marks)

Estimated time: 45-60 minutes

Note prac_q104.c uses the following data type:

C
typedef struct {
    int *data;
    int size;
    int capacity;
} IntStack;

A calculator engine needs a reusable stack of integers that grows on demand so it never overflows a fixed capacity. Implement a small abstract data type: an IntStack backed by a dynamically allocated array that doubles its capacity whenever it fills up.

The IntStack type is a typedef struct provided in the starter header, holding a pointer to the data, the current size, and the current capacity. Implement the four operations below.

Your task is to complete the functions void stack_init(IntStack *s), void stack_push(IntStack *s, int value), int stack_pop(IntStack *s) and void stack_free(IntStack *s), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q104.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q104.c:

TERMINAL
gcc -Wall -Wextra -o prac_q104 prac_q104.c
./prac_q104
1 2 3 p p p
3
2
1

./prac_q104
10 p 20 30 p
10
30

./prac_q104
5 5 p p
5
5

Assumptions / Restrictions / Clarifications

  • stack_init sets up an empty stack with a small initial capacity allocated with malloc.
  • stack_push appends a value, doubling the capacity with realloc when the array is full.
  • stack_pop removes and returns the most recently pushed value; you may assume it is never called on an empty stack.
  • stack_free releases the allocated array and leaves the stack empty; it must not leak memory.
  • Keep size and capacity consistent at all times.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • typedef struct ADTs
  • malloc/realloc/free discipline
  • amortised doubling of capacity
  • last-in-first-out semantics

Worked example

For example:

For the arguments 1 2 3 p p p:

  • 1, 2 and 3 are pushed, growing the array as needed.
  • the three p tokens pop and print 3, then 2, then 1 (last in, first out).

so the program prints 3, 2 and 1 on separate lines.

Edge cases to consider

  • pushing enough values to force at least one realloc
  • popping down to an empty stack
  • interleaved pushes and pops
  • freeing without leaking the backing array

Common mistakes

  • using the old data pointer after realloc moves the block
  • confusing size (used) with capacity (allocated) when deciding to grow
  • forgetting to free the array in stack_free, leaking memory

Optional extension challenge

Add stack_peek, which returns the top without popping, and stack_shrink, which halves capacity when the stack is mostly empty.

You can re-fetch the starter code for this question: prac_q104.c.

Question 105Recursive Directory Size Simulation

(8 marks)

Estimated time: 50-70 minutes

Note prac_q105.c uses the following data type:

C
#define MAX_CHILDREN 20

struct fs_node {
    char name[50];
    int is_directory;
    long size;
    struct fs_node *children[MAX_CHILDREN];
    int num_children;
};

A disk-usage analyser tool (similar to the Unix du command) needs to report how much space a directory and everything nested inside it occupies. A simplified filesystem is modelled as a tree: each struct fs_node (see the provided starter header) is either a file (with a size in bytes and no children) or a directory (size field unused, with a list of child struct fs_node pointers via a children array and num_children count).

Write a recursive function that computes the total size of a node: for a file, its own size; for a directory, the sum of the total sizes of all its children (recursively, since children may themselves be directories).

Your task is to complete the function long total_size(struct fs_node *node), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q105.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q105.c:

TERMINAL
gcc -Wall -Wextra -o prac_q105 prac_q105.c
./prac_q105
3
100 250 50
400

./prac_q105
1
42
42

./prac_q105
0
0

Assumptions / Restrictions / Clarifications

  • A node is a file if is_directory == 0, in which case use its size field.
  • A node is a directory if is_directory == 1, in which case sum over children[0..num_children-1].
  • Directories may be empty (num_children == 0), contributing 0.
  • The tree may be arbitrarily deep, but is guaranteed to have no cycles.
  • num_children never exceeds MAX_CHILDREN as defined in the starter header.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • recursion over a tree of nodes
  • summing across children
  • distinguishing files from directories

Worked example

For example:

For file sizes 100, 250, 50 (100 and 250 directly in root/, 50 nested in root/sub/):

  • root is a directory, so total_size sums over its children: the two direct files (100, 250) and the sub/ directory.
  • total_size on sub/ recurses: it is a directory containing one file of size 50, so it returns 50.
  • summing: 100 + 250 + 50 = 400.

total_size(&root) returns 400.

Edge cases to consider

  • an empty directory (size 0)
  • a single file
  • deeply nested directories
  • a directory containing only empty subdirectories

Common mistakes

  • not recursing into subdirectories, so nested files are missed
  • double-counting a directory's own size and its children's
  • a missing base case causing unbounded recursion

Optional extension challenge

Also return the deepest nesting level and the single largest file found anywhere in the tree.

You can re-fetch the starter code for this question: prac_q105.c.

Question 106Polynomial Linked List Evaluation

(8 marks)

Estimated time: 50-70 minutes

Note prac_q106.c uses the following data type:

C
struct term {
    int coefficient;
    int exponent;
    struct term *next;
};

A computer algebra tool needs to represent sparse polynomials (with many zero coefficients) efficiently, and support evaluating them at a point and adding two polynomials together. A polynomial is represented as a singly linked list of terms, where each term (see the provided starter header) stores an integer coefficient and a non-negative integer exponent, with terms in the list guaranteed to have strictly decreasing exponents.

Write a function that evaluates the polynomial at a given double value of x, and a function that adds two polynomials together, returning a newly allocated list representing the sum (also with strictly decreasing exponents, and with any zero-coefficient terms omitted), without modifying the two input lists.

Your task is to complete the functions double poly_evaluate(struct term *poly, double x) and struct term *poly_add(struct term *poly1, struct term *poly2), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q106.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q106.c:

TERMINAL
gcc -Wall -Wextra -o prac_q106 prac_q106.c
./prac_q106
2.0 3 2 2 1 1 0 , -3 2 5 1
17
7x^1 + 1x^0

./prac_q106
5.0 ,
0
0 (the zero polynomial)

./prac_q106
0.0 2 1 , -2 1
0
0 (the zero polynomial)

Assumptions / Restrictions / Clarifications

  • Either polynomial may be the empty list (NULL), representing the zero polynomial.
  • poly_add must allocate new nodes for the result and must not modify or share nodes with poly1 or poly2.
  • If the resulting sum is the zero polynomial, poly_add should return NULL.
  • Terms with the same exponent in poly1 and poly2 should be combined into a single term in the result (summing their coefficients).
  • The exponents in the returned list must remain strictly decreasing, as in the input lists.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • sparse representation with linked nodes
  • evaluating a polynomial at a point
  • power/coefficient handling

Worked example

For example:

For poly1 = 3x^2 + 2x^1 + 1x^0 evaluated at x = 2.0:

  • the 3x^2 term contributes 3 * 2 * 2 = 12.
  • the 2x^1 term contributes 2 * 2 = 4.
  • the 1x^0 term contributes 1.
  • 12 + 4 + 1 = 17, so poly_evaluate returns 17.

For poly_add with poly2 = -3x^2 + 5x^1: the x^2 terms cancel (3 + -3 = 0, omitted), the x^1 terms combine (2 + 5 = 7), and poly1's x^0 term (1) has no match in poly2, so the sum is 7x^1 + 1x^0.

Edge cases to consider

  • the zero polynomial (empty list)
  • a single constant term (exponent 0)
  • evaluating at x = 0 and x = 1
  • large exponents where naive powers may overflow

Common mistakes

  • recomputing x^n from scratch each term instead of reusing work
  • skipping the constant term when the exponent is 0
  • integer overflow from large exponents or coefficients

Optional extension challenge

Add polynomial addition that merges two sorted-by-exponent lists, combining like terms.

You can re-fetch the starter code for this question: prac_q106.c.

Question 107Maze Solver (Recursive Backtracking)

(8 marks)

Estimated time: 55-75 minutes

A simple puzzle game generates rectangular mazes and needs to check whether a maze is actually solvable before presenting it to the player. A maze is represented as a rows x cols 2D array of characters, where '#' denotes a wall, '.' denotes open floor, 'S' denotes the start cell and 'E' denotes the end cell.

Write a recursive function that determines whether there is a path from the start cell to the end cell, moving only up, down, left or right through open floor cells (not walls), without revisiting a cell already on the current path. To avoid revisiting cells, you should mark visited floor cells (e.g. by temporarily changing them to '#' and restoring them, or using a separate visited array).

Your task is to complete the function int has_path(char maze[20][20], int rows, int cols, int sr, int sc, int er, int ec), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q107.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q107.c:

TERMINAL
gcc -Wall -Wextra -o prac_q107 prac_q107.c
./prac_q107
5 5
S.#..
.##..
...#.
##.#.
....E
0 0 4 4
1

./prac_q107
3 3
S#E
###
...
0 0 0 2
0

./prac_q107
5 5
S.#..
.##..
...#.
##.#.
....E
0 0 0 0
1

Assumptions / Restrictions / Clarifications

  • rows and cols are each at most 20.
  • (sr, sc) and (er, ec) are guaranteed to be valid in-bounds cells that are not walls.
  • The function must terminate for any maze, including one with no valid path.
  • Movement is restricted to the four orthogonal directions (up, down, left, right); diagonal movement is not allowed.
  • The start and end cells may be the same cell, in which case a path trivially exists.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • recursive depth-first search
  • backtracking by unmarking on failure
  • marking visited cells

Worked example

For example:

For the maze S.#..\n.##..\n...#.\n##.#.\n....E with start (0,0) and end (4,4):

  • the recursive search tries moving down/right from the start, backtracking out of dead ends (marking visited cells as '#' temporarily so it never revisits them on the current path).
  • a path exists, e.g. (0,0)->(1,0)->(2,0)->(2,1)->(2,2)->(3,2)->(4,2)->(4,3)->(4,4).

has_path returns 1.

Edge cases to consider

  • start cell equals the goal cell
  • a maze with no possible path
  • a maze that is entirely open
  • a 1x1 maze

Common mistakes

  • not marking cells visited, causing infinite recursion between two cells
  • forgetting to try all four directions
  • marking a cell but never unmarking it when backtracking (if the algorithm needs it)

Optional extension challenge

Return the length of the shortest path (not just whether a path exists) by exploring breadth-first instead.

You can re-fetch the starter code for this question: prac_q107.c.

Question 108Priority Task Queue (Linked List ADT)

(8 marks)

Estimated time: 55-75 minutes

Note prac_q108.c uses the following data type:

C
struct task_node {
    char name[50];
    int priority;
    struct task_node *next;
};

An emergency dispatch system needs to always serve its most urgent outstanding task next, while still processing equally urgent tasks in the order they were reported. Implement a queue of tasks using a singly linked list kept sorted in descending order of priority (higher priority value = more urgent), using the provided task_node struct.

Write a function that inserts a new task into the correct sorted position (allocating a new node), and a function that removes and returns the most urgent task (the head of the list), returning it via an output parameter and freeing its node. Ties in priority should be broken by insertion order (earlier-inserted tasks of equal priority come first).

Your task is to complete the functions struct task_node *pq_insert(struct task_node *head, char *name, int priority) and struct task_node *pq_pop(struct task_node *head, char *name_out, int *priority_out), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q108.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q108.c:

TERMINAL
gcc -Wall -Wextra -o prac_q108 prac_q108.c
./prac_q108
backup 1 fire 9 email 3
Popped: fire 9

./prac_q108
first 5 second 5
Popped: first 5

./prac_q108
only 2
Popped: only 2

Assumptions / Restrictions / Clarifications

  • The list may be empty (head is NULL) when either function is called.
  • pq_pop is never called on an empty list.
  • pq_pop must free the node it removes and return the new head of the list.
  • name_out points to a buffer at least 50 characters long, supplied by the caller.
  • When two tasks share the same priority, the one inserted earlier must be popped first.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • sorted linked-list insertion
  • keeping the highest priority at the front
  • dequeue and free

Worked example

For example:

For inserting backup 1, fire 9, email 3, in that order:

  • backup 1 is inserted into the empty list: [backup 1].
  • fire 9 has higher priority than backup 1, so it goes to the front: [fire 9, backup 1].
  • email 3 is less urgent than fire 9 but more urgent than backup 1, so it goes in between: [fire 9, email 3, backup 1].

pq_pop removes and returns the head, fire 9, so main prints "Popped: fire 9".

Edge cases to consider

  • inserting into an empty queue
  • a new task with the highest priority (new head)
  • equal priorities (define FIFO vs LIFO tie-break)
  • dequeueing until the queue is empty

Common mistakes

  • inserting at the head unconditionally and breaking the ordering
  • losing the list when splicing in a new node
  • leaking the node that is dequeued

Optional extension challenge

Support changing the priority of an already-queued task, re-positioning it to keep the queue ordered.

You can re-fetch the starter code for this question: prac_q108.c.

Question 109Graph Reachability via Adjacency Matrix

(8 marks)

Estimated time: 50-70 minutes

A flight-booking system models direct flight routes between airports as a directed graph, and must check whether one airport can reach another through some sequence of flights. A directed graph of n vertices (0 .. n - 1) is represented as an n x n adjacency matrix of ints, where matrix[i][j] is 1 when the directed edge i -> j exists, and 0 otherwise.

Write a recursive function using depth-first search that determines whether vertex to can be reached starting at vertex from by following directed edges (a vertex is always self-reachable). You will need a helper visited array that prevents infinite recursion on cyclic graphs.

Your task is to complete the function int is_reachable(int matrix[20][20], int n, int from, int to), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q109.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q109.c:

TERMINAL
gcc -Wall -Wextra -o prac_q109 prac_q109.c
./prac_q109
5 0-1 1-2 2-0 3-4 0 2
1

./prac_q109
5 0-1 1-2 2-0 3-4 0 4
0

./prac_q109
5 0-1 1-2 2-0 3-4 3 3
1

Assumptions / Restrictions / Clarifications

  • n is at most 20, matching the fixed array size.
  • The graph may contain cycles; your traversal must not loop forever.
  • 0 <= from && from < n and 0 <= to && to < n.
  • A vertex is always considered self-reachable, even with no outgoing edges (from == to should return 1).
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • adjacency-matrix graphs
  • depth-first or breadth-first traversal
  • a visited[] array

Worked example

For example:

For edges 0-1, 1-2, 2-0, 3-4 and from = 0, to = 2:

  • the search starts at vertex 0, marking it visited.
  • it follows edge 0->1; vertex 1 is then marked visited.
  • it follows edge 1->2 and reaches the target vertex to.

is_reachable returns 1, since the path 0 -> 1 -> 2 reaches 2 when starting at 0.

Edge cases to consider

  • source and destination are equal (reachable trivially)
  • a disconnected graph where the destination is unreachable
  • a graph with self-loops
  • a cycle that must not cause infinite looping

Common mistakes

  • revisiting nodes and looping forever without a visited[] array
  • treating the matrix as undirected when it is directed (or vice versa)
  • indexing the matrix with the vertices swapped

Optional extension challenge

Return the length of the shortest path between two vertices using breadth-first search over the matrix.

You can re-fetch the starter code for this question: prac_q109.c.

Question 110Doubly Linked List Deque ADT

(8 marks)

Estimated time: 55-75 minutes

Note prac_q110.c uses the following data type:

C
struct dnode {
    int value;
    struct dnode *prev;
    struct dnode *next;
};

struct deque {
    struct dnode *head;
    struct dnode *tail;
    int size;
};

A sliding-window algorithm library needs a double-ended queue that supports efficient insertion and removal at both ends, which a singly linked list or plain array cannot provide as cleanly. Implement a double-ended queue (deque) of integers using a doubly linked list, with dnode and deque structs provided in the starter header (the struct deque holds head and tail pointers plus a size count).

Write functions to push a value onto the front, push a value onto the back, pop a value from the front (removing and returning it via an output parameter, returning 1 on success or 0 if the deque was empty), and pop a value from the back (same contract). All four operations must run in O(1) time and must correctly maintain both prev and next pointers as well as the deque's head, tail and size fields in every case, including when the deque becomes empty or has exactly one element.

Your task is to complete the functions void deque_push_front(struct deque *d, int value), void deque_push_back(struct deque *d, int value), int deque_pop_front(struct deque *d, int *value_out) and int deque_pop_back(struct deque *d, int *value_out), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q110.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q110.c:

TERMINAL
gcc -Wall -Wextra -o prac_q110 prac_q110.c
./prac_q110
PB 2 PB 3 PF 1 PRINT POPB POPF
Deque: 1 2 3
Pop back: 3
Pop front: 1

./prac_q110
POPF
Pop front: failed, deque is empty

./prac_q110
PB 7 POPF
Pop front: 7

Assumptions / Restrictions / Clarifications

  • A newly created deque has head == NULL, tail == NULL, size == 0.
  • All push/pop functions must correctly update size.
  • Popped nodes must be freed; no memory may be leaked.
  • After popping the only remaining element, both head and tail must become NULL.
  • Popping from an empty deque must return 0 and leave value_out unwritten (or unspecified), without crashing.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • doubly linked lists with head and tail
  • insert/remove at both ends
  • maintaining prev and next links

Worked example

For example:

For tokens PB 2, PB 3, PF 1, PRINT, POPB, POPF:

  • push_back(2): deque is [2].
  • push_back(3): deque is [2, 3].
  • push_front(1): deque is [1, 2, 3].
  • PRINT prints "Deque: 1 2 3".
  • pop_back removes 3 from the tail, printing "Pop back: 3"; deque is now [1, 2].
  • pop_front removes 1 from the head, printing "Pop front: 1"; deque is now [2].

Edge cases to consider

  • operations on an empty deque
  • a deque with a single element (head == tail)
  • removing the last element (head and tail must become NULL)
  • alternating front/back operations

Common mistakes

  • updating next but forgetting the matching prev pointer
  • not updating tail when removing from the front (or head when removing from the back)
  • leaking removed nodes or dereferencing NULL on an empty deque

Optional extension challenge

Add a function that reverses the deque in O(1) by conceptually swapping the roles of prev/next and head/tail.

You can re-fetch the starter code for this question: prac_q110.c.

Question 111Hash Table with Chaining (Simplified)

(8 marks)

Estimated time: 55-75 minutes

Note prac_q111.c uses the following data type:

C
#define TABLE_SIZE 101

struct hash_node {
    char key[50];
    int value;
    struct hash_node *next;
};

// A simple djb2-style string hash, already implemented for you.
static unsigned int hash_string(char *key) {
    unsigned int hash = 5381;
    for (int i = 0; key[i] != '\0'; i++) {
        hash = hash * 33 + (unsigned char) key[i];
    }
    return hash % TABLE_SIZE;
}

A configuration system for an application needs fast lookup of string-named settings (e.g. "volume" -> 80, "brightness" -> 50) without scanning a list every time. Implement a very small string-to-int hash table using separate chaining, backed by a fixed-size array of TABLE_SIZE linked-list buckets, using the provided starter header (which defines TABLE_SIZE, a struct hash_node for chain entries, and provides a simple hash function hash_string, already implemented for you).

Write a function that inserts a key/value pair into the table (or updates the value if the key already exists, without creating a duplicate node), and a function that looks up a key, returning 1 and writing the value via an output parameter if found, or returning 0 if not found.

Your task is to complete the functions void hash_insert(struct hash_node *table[], char *key, int value) and int hash_lookup(struct hash_node *table[], char *key, int *value_out), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q111.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q111.c:

TERMINAL
gcc -Wall -Wextra -o prac_q111 prac_q111.c
./prac_q111
apples 5 bananas 12 apples 7 LOOKUP apples pears
found, value 7
not found

./prac_q111
LOOKUP anything
not found

./prac_q111
count 1 count 2 count 3 LOOKUP count
found, value 3

Assumptions / Restrictions / Clarifications

  • table is an array of TABLE_SIZE bucket head pointers, each initially NULL.
  • Keys are at most 49 characters long.
  • hash_insert must allocate new nodes with malloc as needed and must not leak memory when updating an existing key's value (it must not allocate a new node if the key already exists).
  • You should use the provided hash_string function to select a bucket.
  • Two different keys may hash to the same bucket (a collision); such keys must both be stored correctly in that bucket's chain.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • hashing keys into buckets
  • separate chaining with linked lists
  • insert and lookup

Worked example

For example:

For inserting apples 5, bananas 12, apples 7 (in that order), then looking up apples:

  • apples 5 is inserted as a new node.
  • bananas 12 is inserted as a new node (in whichever bucket it hashes to).
  • apples 7 finds the existing "apples" node already in the table and updates its value to 7, without allocating a new node.

Looking up "apples" finds the node and reports its current value, 7, so the program prints found, value 7.

Edge cases to consider

  • a lookup in an empty table (miss)
  • two keys that hash to the same bucket (a collision)
  • inserting a key that already exists (update vs duplicate)
  • freeing every bucket chain without leaks

Common mistakes

  • not handling collisions, so a second key overwrites the first bucket slot
  • a hash function that ignores part of the key and clusters badly
  • leaking chain nodes when freeing the table

Optional extension challenge

Add automatic resizing/rehashing when the load factor grows too large, to keep chains short.

You can re-fetch the starter code for this question: prac_q111.c.

Question 112Simulated Library ADT

(8 marks)

Estimated time: 35-50 minutes

Note prac_q112.c uses the following data type:

C
struct book {
    char title[100];
    int year;
};

struct catalogue {
    struct book *books;
    int num_books;
    int capacity;
};

A small community library wants a simple in-memory catalogue system to track which books it owns, that can grow to hold any number of books without the programmer having to guess a fixed maximum size up front. You are to implement a small library-catalogue ADT backed by a dynamically resized array of book structs (provided in the starter header, along with a struct catalogue holding a pointer to the array, the current number of books, and the currently allocated capacity).

Write a function that adds a new book to the catalogue, growing the underlying array with realloc (doubling its capacity) whenever it is full, and a function that removes the book at a given index, shifting subsequent books down by one position to keep the array contiguous.

Your task is to complete the functions void catalogue_add(struct catalogue *cat, struct book b) and void catalogue_remove(struct catalogue *cat, int index), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q112.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q112.c:

TERMINAL
gcc -Wall -Wextra -o prac_q112 prac_q112.c
./prac_q112
CProgramming 1978 TheCppBook 1985 REMOVE 0
1 book(s) remaining: TheCppBook

./prac_q112
TheCppBook 1985 REMOVE 0
0 book(s) remaining

./prac_q112
a 1 b 2 c 3 d 4 e 5 REMOVE 0
4 book(s) remaining: b c d e

Assumptions / Restrictions / Clarifications

  • A new, empty catalogue has num_books == 0 and capacity == 0, with books == NULL.
  • catalogue_add must use realloc (not just malloc a huge array up front).
  • When the array needs to grow, its capacity should double (starting from an initial capacity of 1 when going from empty to non-empty).
  • index passed to catalogue_remove is always valid (0 <= index && index < num_books).
  • catalogue_remove must not leave a gap in the array; subsequent books must be shifted down by one position.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • designing a multi-function ADT
  • dynamic array management of book records
  • inserting and removing items by index

Worked example

For example:

For adding CProgramming (1978) and TheCppBook (1985), then removing index 0:

  • catalogue_add(CProgramming): capacity grows from 0 to 1, num_books becomes 1.
  • catalogue_add(TheCppBook): the array is full (1 of 1), so capacity doubles to 2, num_books becomes 2.
  • catalogue_remove(0): TheCppBook (index 1) is shifted down into index 0, num_books becomes 1.

main prints "1 book(s) remaining: TheCppBook".

Edge cases to consider

  • adding to an empty catalogue
  • removing the first or last book
  • removing the only remaining book from the catalogue
  • resizing the catalogue when capacity is reached

Common mistakes

  • forgetting to shift subsequent elements left on remove
  • not freeing book title strings when removing or destroying catalogue
  • off-by-one when checking catalogue bounds

Optional extension challenge

Add an author field to struct book and a function int catalogue_count_by_author(...).

You can re-fetch the starter code for this question: prac_q112.c.

Question 113CSV Inventory Report

(8 marks)

Estimated time: 45-60 minutes

Note prac_q113.c uses the following data type:

C
struct item {
    char name[50];
    int quantity;
    double unit_price;
};

A stock-control tool imports inventory from a CSV file exported by a supplier. Each line of the file is formatted as name,quantity,price (for example Widget,10,2.50), with no spaces around the commas. Write a function that reads such a file, computes the total value of the inventory (the sum over all items of quantity times price), returns that total, and reports how many items were read via an output parameter.

The struct item type used to hold each record is provided in the starter header.

Your task is to complete the function double inventory_value_from_file(char *filename, int *out_count), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q113.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q113.c:

TERMINAL
gcc -Wall -Wextra -o prac_q113 prac_q113.c
./prac_q113
stock.csv
3 items, total value 75.00

./prac_q113
missing.csv
0 items, total value -1.00

Assumptions / Restrictions / Clarifications

  • If the file cannot be opened, set *out_count to 0 and return -1.0.
  • Each line is well-formed as name,quantity,price with no surrounding whitespace; the name is at most 49 characters.
  • The total value is the sum of quantity * unit_price over every item.
  • An empty file has 0 items and a total of 0.0.
  • Remember to close the file before returning.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • file handling with fopen/fscanf
  • parsing structured CSV fields
  • accumulating a floating-point total into a struct-based record

Worked example

For example:

For stock.csv containing "Widget,10,2.50\nGadget,3,15.00\nBolt,100,0.05\n":

  • Widget contributes 10 * 2.50 = 25.00.
  • Gadget contributes 3 * 15.00 = 45.00.
  • Bolt contributes 100 * 0.05 = 5.00.
  • the total is 75.00 across 3 items, so the program prints "3 items, total value 75.00".

Edge cases to consider

  • a file that cannot be opened (return -1.0, count 0)
  • an empty file (0 items, total 0.0)
  • an item with quantity 0
  • prices that accumulate floating-point rounding

Common mistakes

  • not checking fopen for NULL
  • using %d for the price instead of %lf
  • forgetting the leading space in the fscanf format, so newlines break parsing

Optional extension challenge

Also identify and report the single most valuable line item (largest quantity * price) via extra output parameters.

You can re-fetch the starter code for this question: prac_q113.c.

Question 114Custom Dynamic Array ADT with Iterators

(9 marks)

Estimated time: 40-55 minutes

Note prac_q114.c uses the following data type:

C
struct vec {
    double *data;
    int count;
    int capacity;
};

A scientific data-logging tool needs a growable list of double-precision sensor readings that can be appended to continuously and occasionally have erroneous readings removed from the middle. Implement a resizable dynamic array ADT of doubles, struct vec (see the provided starter header, storing a heap-allocated double array, a count of used elements, and an allocated capacity).

Write a function that creates a new empty vec, a function that pushes a new double onto the end (growing capacity by doubling, using realloc, starting from an initial capacity of 1 when empty), a function that removes and returns the value at a given index (shifting later elements down to close the gap), and a function that frees all memory owned by the vec.

Your task is to complete the functions struct vec vec_new(void), void vec_push(struct vec *v, double value), double vec_remove_at(struct vec *v, int index) and void vec_free(struct vec *v), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q114.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q114.c:

TERMINAL
gcc -Wall -Wextra -o prac_q114 prac_q114.c
./prac_q114
1.5 2.5 3.5 REMOVE 1
removed 2.5
vector is now: 1.5 3.5

./prac_q114
9.0 REMOVE 0
removed 9
vector is now:

./prac_q114
1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0 31.0 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0 40.0 41.0 42.0 43.0 44.0 45.0 46.0 47.0 48.0 49.0 50.0
count 50

Assumptions / Restrictions / Clarifications

  • index passed to vec_remove_at is always valid (0 <= index && index < v->count).
  • vec_push must never overflow the allocated capacity; grow before writing if the array is full.
  • vec_free must set v->data to NULL and v->count and v->capacity to 0 after freeing.
  • None of these functions may use a fixed-size array; all storage must be dynamically allocated and resized as needed.
  • A newly created vec (from vec_new) has count == 0 and capacity == 0, with data == NULL.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • realloc-based growable arrays
  • amortised append performance
  • removing elements and maintaining dense contiguous storage

Worked example

For example:

For pushing 1.5, 2.5, 3.5, then removing index 1:

  • after the three pushes, the vec holds [1.5, 2.5, 3.5], count 3.
  • vec_remove_at(1) saves data[1] = 2.5, shifts data[2] = 3.5 down into index 1, and decrements count to 2.

main prints "removed 2.5" then "vector is now: 1.5 3.5".

Edge cases to consider

  • appending to a vector at capacity
  • removing the head or tail element
  • removing the single remaining element
  • freeing an empty vector

Common mistakes

  • confusing size and capacity, overflowing the allocated buffer
  • forgetting to shift elements on remove
  • not freeing the backing array in vec_free

Optional extension challenge

Add vec_insert_at(struct vec *v, int index, int value) which shifts elements to the right to insert.

You can re-fetch the starter code for this question: prac_q114.c.

Question 115Multi-Level Undo Graph Cycle Check

(9 marks)

Estimated time: 60-75 minutes

A build system (similar to make) needs to detect circular dependencies between build targets before attempting a build, since a cycle would make it impossible to determine a valid build order. A directed dependency graph of n build targets (numbered 0 .. n - 1) is given as an n x n adjacency matrix, where matrix[i][j] == 1 means target i depends on target j (j must be built before i).

Write a recursive function, using depth-first search with a 3-state colouring scheme (unvisited / in-progress / done, using an auxiliary state array you declare inside your function), that determines whether the dependency graph contains a cycle (which would make the targets impossible to build in any valid order). Return 1 if a cycle exists, 0 otherwise.

Your task is to complete the function int has_cycle(int matrix[20][20], int n), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q115.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q115.c:

TERMINAL
gcc -Wall -Wextra -o prac_q115 prac_q115.c
./prac_q115
4 0-1 1-2 2-0
1

./prac_q115
4 0-1 0-2 1-3
0

./prac_q115
1 0-0
1

Assumptions / Restrictions / Clarifications

  • n is at most 20, matching the fixed array size.
  • The graph may be disconnected; your search must check every vertex, not just those reachable from vertex 0.
  • A self-loop (matrix[i][i] == 1) counts as a cycle.
  • The 3-state colouring (unvisited / in-progress / done) is required to correctly detect cycles in a directed graph; a simple visited/unvisited scheme is not sufficient.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • graph cycle detection
  • DFS with three-colour marking
  • recursion over dependencies

Worked example

For example:

For edges 0-1, 1-2, 2-0 (n = 4):

  • the search starts at vertex 0, marking it IN_PROGRESS.
  • it follows 0->1, marking 1 IN_PROGRESS, then follows 1->2, marking 2 IN_PROGRESS.
  • starting at 2, it follows 2->0, and finds 0 is still IN_PROGRESS (not yet DONE).

This back-edge to an in-progress vertex means a cycle exists, so has_cycle returns 1.

Edge cases to consider

  • a graph with no edges (no cycle)
  • a self-loop (a node depending on itself)
  • a long dependency chain with no cycle
  • a cycle buried deep in the graph

Common mistakes

  • using a plain visited flag that cannot tell a cross-edge from a genuine back-edge
  • not resetting the in-progress mark when a DFS branch completes
  • missing cycles that do not involve the start node

Optional extension challenge

Produce a full topological ordering when the graph is acyclic, and report the nodes on the cycle when it is not.

You can re-fetch the starter code for this question: prac_q115.c.

Question 116Conway's Game of Life

(15 marks)

Estimated time: several hours

Implement one generation of Conway's Game of Life on a fixed rows by cols board. Each cell is either alive (*) or dead (.). Cells beyond the edge of the board are treated as permanently dead.

The rules, applied to every cell simultaneously based on its eight neighbours: a live cell with two or three live neighbours stays alive, otherwise it dies (loneliness or overcrowding); a dead cell with exactly three live neighbours becomes alive; every other cell stays dead. Write life_step, which advances the whole board by one generation in place.

The crucial subtlety is simultaneity: every cell's new state depends on the OLD neighbour counts, so you must compute the entire next generation into a separate buffer before copying it back. The provided main reads the board and a step count, runs life_step that many times, and prints the final board.

Your task is to complete the function void life_step(char **grid, int rows, int cols), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q116.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q116.c:

TERMINAL
gcc -Wall -Wextra -o prac_q116 prac_q116.c
./prac_q116
5 5 1
.....
.....
.***.
.....
.....
.....
..*..
..*..
..*..
.....

./prac_q116
5 5 2
.....
.....
.***.
.....
.....
.....
.....
.***.
.....
.....

./prac_q116
4 4 5
.**.
.**.
....
....
.**.
.**.
....
....

Assumptions / Restrictions / Clarifications

  • Neighbours are the eight surrounding cells; cells off the board count as dead.
  • All cells update simultaneously from the previous generation, not one at a time.
  • A live cell is the character *; a dead cell is ..
  • grid[r] is a null-terminated string of length cols; keep it null-terminated.
  • Do not print inside life_step; only main prints the board.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • simultaneous update using a double buffer
  • counting neighbours with bounds checks
  • managing a dynamically allocated 2D board
  • translating a rule set into clean conditional logic

Worked example

For example:

For the 5x5 blinker (three cells in a horizontal row) run for 1 step:

  • the two end cells each have only one live neighbour, so they die.
  • the centre cell has two live neighbours and survives.
  • the cells directly above and below the centre each have three live neighbours and are born.

so the row flips to a vertical bar; after 2 steps it returns to horizontal (period 2). A 2x2 block never changes.

Edge cases to consider

  • cells on the border, whose off-board neighbours are dead
  • a still life such as a 2x2 block (unchanged every step)
  • an oscillator such as the blinker (period 2)
  • an empty board staying empty
  • running for zero steps (board printed unchanged)

Common mistakes

  • updating cells in place so later cells see already-updated neighbours
  • counting the cell itself as one of its neighbours
  • reading neighbours off the edge of the board without bounds checks
  • leaking the temporary next-generation buffer each step

Optional extension challenge

Make the board toroidal (edges wrap around), and detect when the pattern becomes static or starts repeating a previous generation, stopping early.

You can re-fetch the starter code for this question: prac_q116.c.

Question 117RPN Calculator with Variables

(16 marks)

Estimated time: several hours

Build the evaluation core of a small stack-based reverse-Polish-notation (RPN) calculator that also supports 26 single-letter variables a through z. Write rpn_eval, which evaluates one space-separated RPN expression.

Each token in expr is either an integer literal, a single lowercase letter naming a variable (whose current value is read from vars), or one of the binary operators +, -, *, /, %. Operators pop the top two values (the second popped is the left operand), and push the result.

A well-formed expression leaves exactly one value on the stack, which you return. If anything goes wrong -- an unknown token, a stack underflow, a division or modulo by zero, or leftover values on the stack -- set *error to 1 and return 0. On success set *error to 0.

The provided main reads lines from standard input. A line of the form x = <expr> evaluates <expr> and stores the result into variable x; any other line is evaluated and its result printed. Undefined variables read as 0.

Your task is to complete the function long rpn_eval(const char *expr, long vars[26], int *error), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q117.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q117.c:

TERMINAL
gcc -Wall -Wextra -o prac_q117 prac_q117.c
./prac_q117
x = 3 4 +
y = x 2 *
y x -
5 1 2 + 4 * + 3 -
x = 7
y = 14
7
14

./prac_q117
10 0 /
z
1 2 3 +
foo
error
0
error
error

Assumptions / Restrictions / Clarifications

  • Tokens are separated by spaces; you may use strtok on a local copy of expr.
  • The second value popped is the left-hand operand: for 6 2 - the result is 4, not -4.
  • Division and modulo by zero must set *error and must not crash.
  • An expression that does not reduce to exactly one value (too few or too many operands) is an error.
  • Do not modify the string expr in place if it is not your own copy; do not print inside rpn_eval.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • implementing a stack machine over tokenised input
  • distinguishing literals, variable names and operators
  • robust error handling through an out-parameter
  • operand-order care for non-commutative operators

Worked example

For example:

For the session x = 3 4 +, y = x 2 *, y x -:

  • 3 4 + pushes 3 then 4, then + pops them and pushes 7, so x becomes 7.
  • x 2 * pushes 7 (the value of x) then 2, then * gives 14, so y becomes 14.
  • y x - pushes 14 then 7, then - computes 14 - 7 = 7.

The final bare expression 5 1 2 + 4 * + 3 - evaluates to 5 + (1+2)*4 - 3 = 14.

Edge cases to consider

  • division or modulo by zero (must report error, not crash)
  • an expression with leftover operands such as 1 2 3 +
  • an operator with too few operands such as +
  • reading an undefined variable, which is defined to be 0
  • an unknown token such as foo

Common mistakes

  • swapping the operands so 6 2 - yields -4 instead of 4
  • calling strtok directly on expr and corrupting the caller's string
  • forgetting to check for an empty stack before popping two values
  • not verifying that exactly one value remains at the end

Optional extension challenge

Add support for a unary neg operator and a dup token that duplicates the top of the stack, and allow multi-letter variable names using a small hash map.

You can re-fetch the starter code for this question: prac_q117.c.

Question 118Maze Solver (Shortest Path, BFS)

(16 marks)

Estimated time: several hours

Given a rectangular grid maze, find the length of the shortest path from the start cell S to the exit cell E, moving only up, down, left or right and never through a wall #. Open cells are .. Write shortest_path, which returns the number of steps in the shortest route, or -1 if the exit is unreachable.

The natural algorithm is a breadth-first search from S: explore the maze in rings of increasing distance so that the first time you reach E you have found a shortest path. You will need your own queue (an array plus head and tail indices) and a visited/distance array indexed by row * cols + col.

The provided main reads the grid dimensions and rows from standard input, calls your function, and prints the result.

Your task is to complete the function int shortest_path(char **grid, int rows, int cols), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q118.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q118.c:

TERMINAL
gcc -Wall -Wextra -o prac_q118 prac_q118.c
./prac_q118
5 5
S....
.###.
.#.#.
.#.#.
...#E
shortest path: 8 steps

./prac_q118
3 3
S#E
###
...
no path

./prac_q118
1 2
SE
shortest path: 1 steps

Assumptions / Restrictions / Clarifications

  • Movement is 4-directional only; diagonal moves are not allowed.
  • There is exactly one S and at most one E; if E is missing or walled off, return -1.
  • The start cell counts as distance 0; each move adds 1.
  • Do not revisit a cell: mark distances as you enqueue to avoid infinite loops.
  • grid[r] is a null-terminated string of length cols.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • breadth-first search on an implicit grid graph
  • implementing a queue with a plain array
  • mapping 2D coordinates to a 1D index
  • distinguishing unreachable from reachable goals

Worked example

For example:

For the 5x5 maze whose only route snakes around the internal walls:

  • BFS explores outward from S one ring at a time, so distances grow by one per move.
  • the first time the search dequeues E, the recorded distance is guaranteed minimal.
  • here the shortest corridor is 8 moves long, so the answer is 8.

The blocked 3x3 maze S#E / ### / ... has walls sealing E off, so the answer is no path.

Edge cases to consider

  • S adjacent to E (answer 1)
  • E completely walled off (answer -1 / no path)
  • a maze with no E at all
  • only one legal route, forcing a long detour
  • avoiding revisiting cells so the search terminates

Common mistakes

  • using depth-first search, which finds a path but not the shortest one
  • marking a cell visited only when dequeuing, allowing duplicates and blow-up
  • walking off the edge of the grid without bounds checks
  • treating diagonal neighbours as reachable

Optional extension challenge

Reconstruct and print the actual shortest path (as a sequence of moves) by storing a parent for each visited cell, and support a W weight tile that costs 2 to enter using Dijkstra's algorithm.

You can re-fetch the starter code for this question: prac_q118.c.

Question 119Resizable Hash Map (Chaining, Resize, Delete)

(17 marks)

Estimated time: several hours

Note prac_q119.c uses the following data type:

C
struct hnode {
    char *key;
    int value;
    struct hnode *next;
};

struct hmap {
    struct hnode **buckets;
    int nbuckets;
    int size;
};

// Provided for you in the starter file:
//   struct hmap *hmap_create(void);
//   unsigned long hash_str(const char *s);        // djb2
//   void hmap_resize(struct hmap *m, int new_n);  // rehash into new_n buckets

Implement the three core operations of a production-style hash map with string keys and integer values. Collisions are handled by separate chaining (a linked list per bucket), and the table automatically grows and rehashes when it becomes too full.

You are given the struct hmap and struct hnode types plus the provided helpers hmap_create, hash_str (the djb2 hash) and hmap_resize. Your job is to write hmap_put, hmap_get and hmap_delete.

hmap_put updates the value if the key already exists, otherwise inserts a new node at the head of the correct bucket; after an insertion, if the number of stored keys exceeds the number of buckets, print [resize <old> -> <new>] and call hmap_resize to double the table. hmap_get returns the value and sets *found; hmap_delete unlinks and frees the matching node, returning 1 if a key was removed and 0 otherwise.

Your task is to complete the functions void hmap_put(struct hmap *m, const char *key, int value), int hmap_get(struct hmap *m, const char *key, int *found) and int hmap_delete(struct hmap *m, const char *key), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q119.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q119.c:

TERMINAL
gcc -Wall -Wextra -o prac_q119 prac_q119.c
./prac_q119
put apple 1
put banana 2
put cherry 3
put date 4
put fig 5
get cherry
size
put apple = 1
put banana = 2
put cherry = 3
put date = 4
[resize 4 -> 8]
put fig = 5
get cherry -> 3
size = 5

./prac_q119
put x 1
get y
del x
get x
del x
size
put x = 1
get y -> (not found)
del x -> ok
get x -> (not found)
del x -> absent
size = 0

Assumptions / Restrictions / Clarifications

  • Bucket index is hash_str(key) % m->nbuckets; recompute it after any resize.
  • Copy the key with strdup when inserting, and free it (and the node) on delete.
  • Keep m->size (number of stored keys) accurate across inserts, updates and deletes.
  • Grow when m->size > m->nbuckets, doubling the bucket count and rehashing every node.
  • Deleting an absent key must return 0 and change nothing.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • separate chaining with per-bucket linked lists
  • growing and rehashing a table on demand
  • pointer-to-pointer deletion from a linked list
  • owning heap memory for keys and nodes

Worked example

For example:

For the commands put apple 1 ... put date 4 then put fig 5:

  • the map starts with 4 buckets; inserting the 5th distinct key makes size (5) exceed nbuckets (4).
  • hmap_put therefore prints [resize 4 -> 8] and rehashes every node into 8 buckets.
  • get cherry still finds its value afterwards because rehashing preserves all keys.

Edge cases to consider

  • updating an existing key (size must not change)
  • getting a key that is not present (*found = 0)
  • deleting an absent key (returns 0, changes nothing)
  • inserting enough distinct keys to trigger a resize
  • deleting the only key in a bucket

Common mistakes

  • reusing a stale bucket index after a resize moved everything
  • forgetting to free the duplicated key string on delete, leaking memory
  • incrementing size when merely updating an existing key
  • losing the rest of a chain by mis-linking during deletion

Optional extension challenge

Add hmap_free that releases every node and key, and shrink the table (halve and rehash) when the load factor drops below one quarter.

You can re-fetch the starter code for this question: prac_q119.c.

Question 120Tiny Regex Matcher (. and *)

(18 marks)

Estimated time: several hours to a full day

Implement a miniature regular-expression matcher, the classic interview-hard problem popularised by Rob Pike. Write regex_match, returning 1 if pattern matches text and 0 otherwise.

The pattern language has four features: an ordinary character matches itself; . matches any single character; c* matches zero or more consecutive copies of the preceding element (a literal or .); a leading ^ anchors the match to the start of the text; and a trailing $ anchors it to the end.

Without a leading ^, the pattern may match anywhere inside text, so a*b matches xxaab. The elegant solution is a pair of mutually recursive helper functions; think carefully about how * tries progressively longer matches while still allowing the rest of the pattern to succeed.

Your task is to complete the function int regex_match(const char *pattern, const char *text), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q120.c also contains a main function which allows you to test your function. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your function and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your function will be called directly during marking. The main function is only to let you test your function.

Here is how the main function allows you to test prac_q120.c:

TERMINAL
gcc -Wall -Wextra -o prac_q120 prac_q120.c
./prac_q120
a*b aaab
c.t cat
x.*z xyyyz
^ab abcd
a*b ~ "aaab": match
c.t ~ "cat": match
x.*z ~ "xyyyz": match
^ab ~ "abcd": match

./prac_q120
a*b b
ab$ crab
a*b ac
c.t ct
^ab bab
a*b ~ "b": match
ab$ ~ "crab": match
a*b ~ "ac": no match
c.t ~ "ct": no match
^ab ~ "bab": no match

Assumptions / Restrictions / Clarifications

  • . matches exactly one character; it does not match the end of the string.
  • * always binds to the single element immediately before it.
  • ^ is only special as the very first character of the pattern; $ only as the very last.
  • The empty pattern matches any text (it matches the empty prefix).
  • Do not use any library regex functions; implement the matching yourself.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • mutual recursion over two strings
  • backtracking implicit in the * case
  • reasoning about base cases and anchors
  • pointer walking through C strings

Worked example

For example:

For a*b against aaab:

  • a* can match zero or more a characters; the matcher tries the longest run first.
  • after consuming aaa, the remaining pattern b matches the final b, so the answer is match.

For ab$ against crab, the $ forces ab to sit at the very end, which it does, so it matches; but a*b against ac fails because no arrangement of a* leaves a b to match.

Edge cases to consider

  • a*b matching b (the star matches zero characters)
  • the anchor $ requiring the text to end exactly
  • the empty pattern, which matches everything
  • . refusing to match past the end of the text
  • a pattern that only matches partway through an unanchored text

Common mistakes

  • treating * as matching the character after it instead of the one before it
  • an infinite loop in the * case from never advancing the text pointer
  • forgetting that . must still consume exactly one real character
  • mishandling ^/$ when they appear (or do not appear) at the ends

Optional extension challenge

Add support for + (one or more) and ? (zero or one), and a character class [...] such as [a-z].

You can re-fetch the starter code for this question: prac_q120.c.

Question 121LRU Cache (Hash Map + Doubly Linked List)

(18 marks)

Estimated time: a full day

Note prac_q121.c uses the following data type:

C
#define NBUCKETS 16

struct entry {
    int key;
    int val;
    struct entry *hnext;         // bucket chain
    struct entry *prev, *next;   // recency list (head = most recent)
};

struct lru {
    int capacity;
    int size;
    struct entry *buckets[NBUCKETS];
    struct entry *head, *tail;   // doubly linked recency list
};

// Provided for you in the starter file:
//   struct lru *lru_create(int capacity);
//   unsigned bucket_of(int key);
//   void list_unlink(struct lru *c, struct entry *e);
//   void list_push_front(struct lru *c, struct entry *e);
//   void bucket_remove(struct lru *c, struct entry *e);

Implement a fixed-capacity Least-Recently-Used (LRU) cache mapping integer keys to integer values. This is a canonical hard interview problem because it demands O(1) lookup AND O(1) recency updates simultaneously, achieved by combining a hash table (bucket chains) with a doubly linked list ordered from most- to least-recently-used.

You are given the structures and all the plumbing: lru_create, the bucket hashing, and the doubly-linked-list helpers list_unlink, list_push_front and bucket_remove. You must write lru_get and lru_put.

lru_get returns the value for a key and moves that node to the front of the recency list, or returns -1 on a miss. lru_put updates an existing key (moving it to the front); on a genuinely new key, if the cache is full it must evict the tail node (the least recently used), printing evict <key>, before inserting the new node at the front.

Your task is to complete the functions int lru_get(struct lru *c, int key) and void lru_put(struct lru *c, int key, int val), whose behaviour is described above. This is the only code you need to write; it will be called directly during marking.

Testing

prac_q121.c also contains a main function which allows you to test your functions. It reads its input from standard input — exactly the lines shown being typed in the session below — then calls your functions and prints the result. You type the input directly at the keyboard (or redirect a file into the program); nothing is passed on the command line.

Do not change this main function. If you want to change it, you have misread the question.

Your functions will be called directly during marking. The main function is only to let you test your functions.

Here is how the main function allows you to test prac_q121.c:

TERMINAL
gcc -Wall -Wextra -o prac_q121 prac_q121.c
./prac_q121
2
put 1 10
put 2 20
get 1
put 3 30
get 2
get 3
get 1 -> 10
evict 2
get 2 -> miss
get 3 -> 30

./prac_q121
2
put 5 50
get 9
put 6 60
put 7 70
get 5
put 6 66
get 6
get 9 -> miss
evict 5
get 5 -> miss
get 6 -> 66

Assumptions / Restrictions / Clarifications

  • Every access (get hit, or put that updates or inserts) makes that key the most recently used.
  • Eviction removes the tail of the recency list, which is the least recently used key.
  • lru_get on a missing key returns -1 without changing the cache.
  • Keep the hash bucket chain and the recency list consistent: a node lives in both at once.
  • Use the provided helpers rather than re-deriving the pointer surgery.
Stuck? Here's a hint

What this practises

This question gives you practice with:

  • combining a hash table with a doubly linked list for O(1) operations
  • maintaining recency order under reads and writes
  • eviction of the least recently used element
  • keeping two data structures pointing at the same nodes consistent

Worked example

For example:

For capacity 2 and the commands put 1 10, put 2 20, get 1, put 3 30:

  • after the two puts the recency order (most to least recent) is 2, 1.
  • get 1 returns 10 and promotes key 1, making the order 1, 2.
  • put 3 30 needs room, so it evicts the least recently used key 2 (printing evict 2) and inserts 3.
  • a later get 2 therefore reports a miss.

Edge cases to consider

  • a get miss on a key never inserted (returns -1)
  • updating an existing key's value (no eviction, but it becomes most recent)
  • a put that evicts because the cache is exactly full
  • re-inserting a key that was previously evicted
  • capacity interacting with which key is considered least recently used

Common mistakes

  • evicting on an update instead of only on a genuinely new key
  • forgetting to remove the evicted node from its hash bucket as well as the list
  • not promoting a node to the front on a get hit
  • reading a freed node after eviction

Optional extension challenge

Add an lru_free that releases every node, and support a del <key> command that removes a key from both the bucket chain and the recency list.

You can re-fetch the starter code for this question: prac_q121.c.