CS1010 Final Exam 2018/29
CS1010 Final Exam 2018/29
SCHOOL OF COMPUTING
FINAL ASSESSMENT FOR
Semester 1 AY2018/2019
INSTRUCTIONS TO CANDIDATES
1. This assessment paper contains 15 questions and comprises 14 printed pages, including this
page.
3. The total marks for this assessment is 70. Answer ALL questions.
5. You can assume that in all the code given, no overflow nor underflow will occur during exe-
cution. In addition, you can assume that all given inputs are valid and fits within the specified
variable type.
6. You can assume all the necessary headers ( math.h , stdbool.h , cs1010.h , etc.) are included.
For brevity, the include directives are not shown. Further, not all variable declarations are
shown. You can assume that all variables are properly declared with the right type.
8. Please write your student number only. Do not write your name.
Final Assessment CS1010 AY18/19 Sem 1
Part I
Multiple Choice Questions (36 points)
For each of the questions below, write your answer in the corresponding answer box on the answer
sheet. Each question is worth 3 points.
If multiple answers are equally appropriate, pick one and write the chosen answer in the answer
box. Do NOT write more than one answer in the answer box.
1. (3 points) Consider the following three functions that take in a long value as input. Which of
these three functions are equivalent? Two functions are equivalent if both return the same value
when given the same input.
bool f(long x) {
if (x % 10 == 0) {
if (x < 0) {
return true;
}
} else {
if (x < 10) {
return true;
}
}
return false;
}
bool g(long x) {
if (x > 10 && x % 10 > 0) {
return false;
}
return true;
}
bool h(long x) {
return (x < 0 && x % 10 == 0) || (x < 10);
}
A. f and g only
B. g and h only
C. g and h only
D. f , g , and h
Write X in the answer box if no two functions are equivalent.
Solution: This question assesses if you understand if - else statements and logical expres-
sions.
X. None of the functions are equivalent. f(0) is false, but g(0) and h(0) are true. g(10) is true,
but f(10) and h(10) are true.
Some students incorrectly think that f and h are equivalent. The function h is actually
equivalent to:
Page 2
Final Assessment CS1010 AY18/19 Sem 1
bool f(long x) {
if (x % 10 == 0) {
if (x < 0) {
return true;
}
}
if (x < 10) {
return true;
}
return false;
}
One of B or C should be f and h only. I am sorry for the typo but luckily this does not
affect the answer. In fact, I think this typo saves some students who think that f and h are
equivalent from choosing the wrong answer :) For this reason, as well, I decided not to issue
a correction during the exam despite many questions from you.
Page 3
Final Assessment CS1010 AY18/19 Sem 1
2. (3 points) Consider the following three functions that take in an array with at least one element as
input. Which of these three functions are equivalent? Two functions are equivalent if they always
return the same value when given the same input.
long foo(long len, long a[len]) {
for (long i = 0; i < len; i += 1) {
if (a[i] % 2 == 0) {
return i;
}
}
return len;
}
Solution: This question assesses if you understand how arrays and the three looping struc-
tures in C behave.
In the given functions, bar always returns i that is larger than the other two (due to i += 1;
even after we found the even element in array a .
To work as intended (return the index of the first even array element, the code should look
like:
Page 4
Final Assessment CS1010 AY18/19 Sem 1
done = true;
} else {
i += 1;
}
}
return i;
}
or,
In qux , since the loop condition checks a[i] % 2 != 0 first before i < len , the code will
access a[len] , which could be illegal if the input array is of length len . We cannot predict
what would happen if such an illegal read occurs (the C standard calls this undefined behavior).
In foo , no illegal access would occur as only array elements a[0] to a[len-1] are read.
The answer is X.
However, your experience with clang on Ubuntu is that just reading the illegal elements
would not crash the program and would only cause garbage values to be read. Here, since we
exit the loop anyway, it does not change the behavior of the program. As such, we also accept
B ( foo and qux are equivalent) as the answer.
Page 5
Final Assessment CS1010 AY18/19 Sem 1
Solution: A.
moo would lead to an illegal write to a[len] . baa would lead to illegal read (in print) since
the string is no longer null-terminated ( a[len-1] is set to 'a' ). quack would read an
uninitialized char but it is not illegal memory access.
This question assesses if you are familiar with the convention of strings as well as the bounds
of an array.
Page 6
Final Assessment CS1010 AY18/19 Sem 1
Solution: X.
This question checks if you are familiar with the type double in C.
Update: Originally this is meant to be a straightforward question, but there is a twist to the
story here. The double type cannot represent all possible floating point numbers, as you
know. Due to how a floating number is represented, it turns out that, not all integers can be
represented. For instance, between the range 253 − 254 , only even numbers are represented.
Thus, since d is uninitialized, there is a chance that the initial value of d is an even number
somewhere in this range. In which case, the statement d -= 1 has no effect, and the loop
above does loop forever! We accept D as an answer to this question as well.
What is the value of double_average , round to the nearest integer, after executing the three
lines of code below?
long x = 10;
long y = 20;
double double_average = 2*AVERAGE(x, y);
A. 20
B. 25
C. 30
D. 35
E. 40
Write X in the answer box if none of the answers above is correct.
Page 7
Final Assessment CS1010 AY18/19 Sem 1
Solution: A. The expression gets expended into 2*0.5*x + 0.5*y = x + 0.5*y = 20.
This question checks if you are aware of how macro works in C.
Page 8
Final Assessment CS1010 AY18/19 Sem 1
int main()
{
double *ptr;
double x;
double y;
ptr = &y;
// Line B
}
Which of the following function invocation at Line B would cause the assertion ptr == &x to be
true?
A. tata(ptr, x);
B. titi(ptr, &x);
C. tete(&ptr, x);
D. tutu(&ptr, &x);
E. tutu(*ptr, *x);
Write X in the answer box if none of the answers above is correct.
Solution: D.
Here, we need to write a function that changes the ptr so that it points to the address of x .
We must have access to the address of x within the function. This observation eliminates
choices A and C, which pass the value of x into the function.
We also need to update ptr to point to a new location – hence we need to pass ptr by
reference to the function. This eliminates A and B.
Only D, E, and X remain. A quick check should eliminate E since the type does not even match.
Let’s check if tutu does what it is supposed to do: *ptr = x . The x in tutu points to the
x in main . The ptr in tutu points to the ptr in main . *ptr = x in tutu thus makes
the ptr in main points to x in main .o
It is useful to draw the call stack to understand the intricacy of this. The following shows the
different versions of the functions just before tata/titi/tete/tutu exits.
Page 9
Final Assessment CS1010 AY18/19 Sem 1
x x x x x
x x x x
y y y y
Page 10
Final Assessment CS1010 AY18/19 Sem 1
A. egg only
B. cheese only
C. ham and cheese only
D. egg and ham only
E. egg , ham , and cheese
Write X in the answer box if none of the combinations above is correct.
Solution: The outer loop of egg loops O(n/4) time, while the inner loop loops O(n) time.
Therefore, egg runs in O(n2 ) time.
The outer loop of ham loops O(n) time, while the inner loop also loops O(n) time. Therefore,
ham also runs in O(n2 ) time.
√ √
The outer loop of cheese loops O(n n) time, while the inner loop loops O( n) time. There-
√ √
fore, ham also runs in O(n n × n) = O(n2 ) time.
The answer is E.
Page 11
Final Assessment CS1010 AY18/19 Sem 1
What is the worst-case running time of mystery , expressed using Big-O notation, when the input
is an array of size n?
A. O(n2 )
B. O(n log n)
C. O(n)
D. O(log2 n)
E. O(log n)
Write X in the answer box if none of the answers above is correct.
Solution: This question checks if you know how to analyze the running time of a recursive
function. I botched this question badly due to the typo :(
This intended behavior of the function is half the array at every recursive call, and have a
linear scan in each call. So the running time can be expressed as T (n) = n + T (n/2), which,
after expansion, becomes T (n) = n + n/2 + n/4 + n/8 + ...1 which is O(n).
Our first error is the typo in the question ( end > start ), which would cause the function to
be O(1). Our second error is to correct it into ( end < start ), which could cause the function
to recurse forever.
It should be end <= start .
We accept both C O(n) and X (infinite loop) as the answer.
Page 12
Final Assessment CS1010 AY18/19 Sem 1
9. (3 points) Suppose we have an array long a[3] = {3, 1, 2}; . What is the content of the array
a after calling do_something(3, a); ?
A. {3, 2, 1}
B. {1, 2, 3}
C. {3, 3, 3}
D. {3, 1, 2}
E. {2, 1, 3}
Write X in the answer box if none of the answers above is correct.
Solution: B.
This is a bit tedious but quite straightforward as you only need to trace through the code
carefully step-by-step.
10. (3 points) Which of the following is a correct assertion at Line A of the do_something ?
A. { curr != 0 && a[curr-1] > a[curr-2] }
B. { curr != 0 && a[curr] > a[curr-1] }
C. { curr != 0 && a[curr + 1] > a[curr] }
D. { curr >= 0 && a[curr] > a[curr-1] }
E. { curr >= 0 && a[curr + 1] > a[curr] }
Write X in the answer box if none of the answers above is correct.
Solution: E.
Line A is in the else block. At the beginning of this block, curr == 0 || a[curr] >= a[curr-1]
is false. Applying De Morgan’s law, we know what curr != 0 && a[curr] < a[curr-1] is
true.
Page 13
Final Assessment CS1010 AY18/19 Sem 1
The next three lines swap a[curr] with a[curr-1] . After swapping, we know that the
assertion curr != 0 && a[curr] > a[curr - 1] must be true.
The final line of code before Line A is curr -= 1 . Let’s analyze the easy part first, we pre-
viously have a[curr] > a[curr - 1] , and now that curr is one less than before, the fol-
lowing must be true: a[curr + 1] > a[curr] .
This eliminates A, B, and D.
What about curr != 0 ? After we decrement curr by 1, curr can now be 0 and so can be
anything. Choice C is eliminated. We only need to verify that E is correct.
We need a stronger constraint on curr . Since we are accessing a[curr] , it is a hint that
curr is no less than 0. (You should observe this pattern when you solve Question 9). A quick
check on the code can confirm this: whenever curr reaches 0, the true block of the if
statement increments curr so that curr is 1. So curr >= 0 and E is correct.
Side note: this algorithm is a single loop O(n2 ) sorting algorithm called stupid sort or gnome
sort.
Page 14
Final Assessment CS1010 AY18/19 Sem 1
struct obj {
long *ptr;
long id;
};
int main()
{
struct obj o;
long x = 10;
o.ptr = &x;
o.id = 0;
blah(&o);
cs1010_println_long(o.id);
cs1010_println_long(*(o.ptr));
Solution: E.
Another pointer and call stack question – this time with struct .
As usual, it is useful to draw the call stack and trace through:
Page 15
Final Assessment CS1010 AY18/19 Sem 1
blah blah
optr optr
10 X 10 X 10 X
After calling blah , the pointer o.ptr points to o.id , so the print statements print out 20
and 20.
Page 16
Final Assessment CS1010 AY18/19 Sem 1
Which of the following statement(s) is/are true about the code above:
(i) If the input array a contains n elements, all has the same value, insertion_sort takes
O(n2 ) time.
(ii) If the input array a contains n distinct elements that are sorted in descending order, insertion_sort
takes O(n) time.
(iii) If the input array a contains n distinct elements that are sorted in ascending order, insertion_sort
takes O(1) time.
A. Only (i)
B. Only (i) and (ii)
C. Only (i) and (iii)
D. Only (ii) and (iii)
E. (i), (ii), and (iii)
Write X in the answer box if none of the combinations is correct.
Solution: A.
This is similar to the insertion sort algorithm given in the lecture notes, except that on Line 5,
the continuing condition of the while loop is changed to temp <= a[i] && i >= 0 instead
of the original temp < a[i] && i >= 0 . Because of this, this implementation will continue
to scan the array even if it finds a value that is strictly smaller than the value to be inserted.
Thus, (i) is true. Given an array with n elements of the same value, the algorithm still scans
and re-inserts every one of the elements.
(ii) is false. An array that is inversely sorted is like the worst enemy of insertion sort. It will
take O(n2 ).
(iii) is false. An array that is sorted is the best friend of insertion sort, but unfortunately, we
still have to scan through the list to make sure that it is sorted. It will take O(n) time.
Page 17
Final Assessment CS1010 AY18/19 Sem 1
Part II
13. (6 points) Binary. The following program binary generates all binary strings (i.e., strings con-
sisting of '0' and '1' only) of a given length n recursively. For instance
ooiwt@pe101:~$ ./binary
2
00
01
10
11
ooiwt@pe101:~$ ./binary
3
000
001
010
011
100
101
110
111
The function generate below recursively generates all binary substrings of length n - k and
prints out the binary string. The recursive calls, however, are missing.
Complete the function generate . Write only the missing lines on the answer sheet.
void generate(long n, char str[], long k) {
if (k == n-1) {
str[k] = '0';
cs1010_println_string(str);
str[k] = '1';
cs1010_println_string(str);
return;
}
// Missing Lines
str[k] = '0';
generate(n, str, k+1);
str[k] = '1';
generate(n, str, k+1);
Page 18
Final Assessment CS1010 AY18/19 Sem 1
We do not deduct marks for syntax errors. If you get the four lines above in the right order,
you should receive full marks.
A common bug is to do
str[k] = '0';
generate(n, str, k+1);
str[k] = '1';
Many students incorrectly print out the string in the answers, you will get 1 mark deducted.
Besides the above, there are many other variations of the solution, we try to give partial marks
if possible. But the following solutions are among the popular ones that we did not give any
marks:
or
// swap something
swap(..);
generate(n, str, k+1);
// swap back
swap(..);
Page 19
Final Assessment CS1010 AY18/19 Sem 1
that looks for the item q in an array list , among items list[i] .. list[j] using binary
search. The code for this function is omitted.
In an attempt to try to speed up binary search on a large array, Mario wrote the following function
to find the starting point and end point in the array that contains q .
long narrowing_then_search(const long list[], long len, long q) {
long start = 0;
long end = 1;
do {
if (q == list[end]) {
return end;
}
if (q < list[end]) {
// Line F
return binsearch(list, start, end, q);
}
start = end;
end += 10; // Line G
// Line H
} while (end < len);
// Line I
return binsearch(list, start, len-1, q);
}
You can assume that the input array is already sorted in non-decreasing order.
(a) (3 points) Write an assertion that relates q to list[start] on Line H.
(b) (3 points) Write an assertion that relates q to list[start] and list[end] on Line F.
(c) (3 points) Write an assertion that relates q to list[start] on Line I.
(d) (3 points) What is the worst case running time, in Big-O notation, of this algorithm?
(e) (3 points) Suppose we change Line G to
end *= 2;
What is the worst-case running time, in Big-O notation, now?
Solution: The question does not assume that q must be somewhere between the list. So the
proper answer should be the following.
(a) q > list[start] .
(d) O(n)
(e) O(log n)
The idea behind Mario’s algorithm is to chop the list into smaller ones, of size 10 each, then
perform a binary search on the shortened list. (This turns out to be a bad idea, but, let’s answer
the questions first).
Page 20
Final Assessment CS1010 AY18/19 Sem 1
On Line H, we have “survived” the checks that q == list[end] and q < list[end] , so
we know that q > list[end] and so q > list[start] just before Line G. Line G does not
change start nor q , so that property q > list[start] still holds at Line H.
In the second iteration onwards, the property q > list[start] is true at Line F. And if we
assume that q >= list[0] , we can ensure that list[start] <= q < list[end] at Line F.
This property ensures that we can find q among list[start..end] with binary search.
Since we know q > list[start] holds at Line H, when we exit the loop, this property still
holds. This property ensures that we can again perform a binary search for q in the remaining
elements of the list.
Mario intends to speed up the search, but his algorithm actually runs in O(n) time, since in
the worst case, the algorithm needs to check n/10 times to find the right 10-element list to
search for q . Binary search here runs in O(1) time since the sub-array to binary-search has
at most 10 elements.
But, Mario’s intention is not wrong. His algorithm can be fixed by changing Line G to end *= 2 .
The assertions above remain, but now, the algorithm needs to check only log n times to find
the right sub-array to search for q . In the worst case, the size of the sub-array is n/2 = O(n).
But the binary search on this is still O(log n), giving the total running time of O(log n).
Side note: After fixing Line G, the algorithm above becomes a search algorithm called exponen-
tial search. In practice, it outperforms binary search, especially when the item to search for is
near the beginning of the array. Suppose the index of q is i. Then exponential search takes
O(log i) time to find. Our traditional binary search always takes O(log n) independent of i.
Page 21
Final Assessment CS1010 AY18/19 Sem 1
int main() {
long x[2] = {-5, 10};
long *p;
p = x;
f(*p, x);
// Line E
}
(a) (4 points) The types for the parameters a and b to function f is missing. Fill in the correct
type of the parameters so that the compiler does not report any error or warning.
(b) (7 points) Draw the content of the call stack when the execution reaches Line D using the
notations similar to what has been used in CS1010. Label all your call frames, variables, and
values on the call stack. You may use arrows to denote pointers, instead of using the actual
memory addresses.
(c) (2 points) What are the values in the array x after calling f , at Line E?
Solution:
(a) void f(long a, long * b)
Two marks for each type. This should be easy since you have read/written many pro-
grams that involve passing in arrays and pointers into a function.
(b)
Getting f , main , b , a , p , x[1] , x[0] correct will give you one mark each. If you add
extra stuff that does not belong to the stack, we deduct one mark each for each variable
that does not belong to the stack.
We do not double penalize, so if your answer in (c) is wrong and is consistent with the
content of x in (c), we do not penalize again in (b).
Page 22
Final Assessment CS1010 AY18/19 Sem 1
• Putting the array x[] in the heap or outside the call stack.
• Drawing f , x (in addition to the x[0] and x[1] ), and b+1 on the call stack.
• Treating b as an array ( b is just a pointer).
• Storing *p , *b , etc on the stack. (Only the pointers are on the stack!)
• Drawing main and f out of order ( main should be at the bottom). But we do not
penalize for this.
• Drawing the call frame for f inside the call frame for main . This is usually done
to languages with nested functions but not to C.
END OF PAPER
Page 23