Final 2223 s2 W Solution
Final 2223 s2 W Solution
SCHOOL OF COMPUTING
MIDTERM ASSESSMENT FOR
Semester 2 AY2022/2023
INSTRUCTIONS TO CANDIDATES
1. This assessment paper contains 19 questions and comprises 14 printed pages, including this page.
2. Write all your answers in the answer sheet provided.
3. The total marks for this assessment is 100. Answer ALL questions.
4. This is an OPEN BOOK assessment. You are only allowed to refer to hard-copy materials.
5. All programs in this assessment paper use Java 17 and are compiled with the flags -Xlint:unchecked
and -Xlint:rawtypes .
6. Import statements for all programs are omitted. You may assume that the necessary classes are imported.
7. An abridged API for Java Stream can be found on Page 14.
Question Points
1 - 11 33
12 4
13 7
14 8
15 12
16 9
17 6
18 15
19 6
TOTAL 100
Page 1
Final Assessment CS2030S AY22/23 Sem 2
Solution: B. Generics
2. The NullPointerException is thrown when the code tries to use a member of a null object. Which of
the following concepts in CS2030S helps to reduce the chances of NullPointerException being thrown
during run time?
A. Dynamic binding
B. Type inference
C. The Maybe Monad
D. Lazy evaluation
E. Pokémon exceptions
Shade X in the answer box if none of the above is correct.
3. Writing code with control flow and logic that is easy to reason about leads to code that is easier to debug
and maintain. Which of the following concepts in CS2030S does NOT help to make the code easier to reason
about?
A. The Liskov Substitution Principle
B. Referential transparency
C. Immutable classes
D. Pure functions
E. Aliasing
Shade X in the answer box if none of the above is correct.
Page 2
Final Assessment CS2030S AY22/23 Sem 2
Solution: E. Aliasing
4. Software evolves over time. Changes in one part of the software can impact other parts. Which of the
following concepts in CS2030S does NOT help in limiting the impact of code changes as software evolves?
A. The Liskov Substitution Principle
B. Information hiding
C. Tell, don’t ask
D. Method overloading
E. Pure function
Shade X in the answer box if none of the above is correct.
Which of the following can be determined by the Java compiler when the program above is compiled?
A. The value of parameter i at Line A.
B. The run-time type of parameter z at Line A
C. Whether Line A will ever be reached during execution.
D. Which implementation of foo is invoked at Line A.
E. Which implementation of bar is invoked at Line B.
Shade X in the answer box if none of the above is correct.
Page 3
Final Assessment CS2030S AY22/23 Sem 2
class Rectangle {
private Point topLeft;
private Point lowerRight;
:
}
Details of the two classes Point and Rectangle are omitted. The relationship between the classes Point
and Rectangle is called:
A. inheritance
B. covariance
C. composition
D. subtyping
E. nesting class
Shade X in the answer box if none of the above is correct.
Solution: C. composition.
Page 4
Final Assessment CS2030S AY22/23 Sem 2
and variables
Object o;
I i;
A a;
AI ai;
AI1 ai1;
Which of the following method invocations would lead to a type mismatch and thus a compilation error?
A. Box.foo(Box.of(o));
B. Box.foo(Box.of(i));
C. Box.foo(Box.of(a));
D. Box.foo(Box.of(ai));
E. Box.foo(Box.of(ai1));
Solution: X. The T in Box<T> , of , and foo are three different versions of T . So they can be
inferred to be three different types.
p.produce();
}
}
Which of the following statements is correct about the state of the stack when the program executes and
reaches Line C (following CS2030S convention)? Here, the stack refers to the stack in the stack and heap
diagram.
A. The stack is empty.
Page 5
Final Assessment CS2030S AY22/23 Sem 2
Solution: C. Only the stack frame for main and produce are on the stack.
Line C is inside the lambda and this lambda is (as per our convention) used in an anonymous class
extending Producer that has a single abstract method called produce . So, produce is in the stack
as we are still inside the method produce .
of is not in the stack because Box.of(5) has finished evaluation.
Page 6
Final Assessment CS2030S AY22/23 Sem 2
class A {
public A foo1(A x) {
return null;
}
public A foo2(A x, A y) {
return null;
}
}
Solution: B. A::foo2 .
The assignments are as follows:
• f2 = A::foo1;
• f1 = A::foo1;
• f2 = A::foo2;
• f1 = A::foo2;
• f2 = A::foo2;
Page 7
Final Assessment CS2030S AY22/23 Sem 2
The method doSomething takes a non-deterministic amount of time each time it is called. Which of the
following statement is NOT correct about the possible printed values?
A. If 1 is printed, it is always printed after 4.
B. If 2 is printed, it is always printed after 4.
C. If 3 is printed, it is always printed after 4.
D. If 3 is printed, it is always printed before 2 and 1.
E. 1, 2, and 4 are always printed (but not necessary in this order).
Shade X in the answer box if none of the choices above is the answer.
Page 8
Final Assessment CS2030S AY22/23 Sem 2
class W {
static void m1() throws E1 {
m2();
// Line D
m2();
// Line E
}
Trace through the execution flow of the program when the program is executed, and write down the lines
that would be reached during the execution in order.
For example, if the execution reaches Lines D, E, F, and G in that order, write DEFG on the answer sheet.
Solution: JL
Note that the mark // Line X is exactly at that particular line and not the line above it. So // Line D
is only reached if and only if the first call of m2(); terminates normally without exception.
Further, note that although m1 is declared with throws E1 , it is actually throwing new E2(); . So it
will go to // Line J . This skips all other lines including // Line K and goes straight to // Line L
because finally is always executed.
• 4 marks for JL
Page 9
Final Assessment CS2030S AY22/23 Sem 2
13. (7 points) What does each of the following stream pipelines print?
(a) (3 points) dummy
Stream.of(1, 2, 3, 4)
.map(n -> Stream.iterate(1, i -> i + 1)
.limit(n)
.map(x -> x.toString())
.reduce("", (x, y) -> x + y))
.forEach(x -> System.out.print(x + " "));
Solution: 2 4 4 6 8 6 12
The table:
Operation Elements
of 1 2 3
flatMap + map 2 3 4 4 6 8 3 6 9
filter 2 - 4 4 6 8 - 6 -
Page 10
Final Assessment CS2030S AY22/23 Sem 2
14. (8 points) A store has a program for calculating the discount prices on all products. For their big sale, there
is a requirement that for products above $100, there is a discount of 10%.
Consider the class SaleDiscount :
class SaleDiscount {
private double price;
private int productID;
Sales are not going well, so the store owners decide to also discount 50% off of all products but only for
products costing $100 or lower. Consider the class FireSaleDiscount :
class FireSaleDiscount extends SaleDiscount {
public FireSaleDiscount(double price, int productName) {
super(price, productName);
}
@Override
public void discount() {
double price = this.getPrice();
double discount = (price > 100) ? 0.90 : 0.5;
this.setPrice(discount * price);
}
}
(a) (3 points) Does the class FireSaleDiscount violate the Liskov Substitution Principle? If so, why? If
not, why not?
Solution: Yes, it is substitutable, the discount method does not change the desirable property
of the parent method. It will still discount those above $100 correctly (10% off).
• In LSP, we substitute superclass with subclass and not the other way around.
• The desirable property is conditioned on products above $100. Products with a price smaller
than or equal to $100 are not part of the desired property.
Page 11
Final Assessment CS2030S AY22/23 Sem 2
jPhone.discount();
}
}
Draw the stack and heap diagram, when the program reaches Line M. Line M is described in the SaleDiscount
class. Label your stack frames, objects, and variables clearly. You may omit args in your diagram.
Solution:
Stack Heap
SaleDiscount
price 1080
y: 1
productID 2030
discount
this
SaleDiscount
jPhonePlus
1 mark deduction per mistake, which includes (but is not limited to):
• Wrong productID
Page 12
Final Assessment CS2030S AY22/23 Sem 2
class B extends A {
public void f(A param) { }
public void f(B param) { }
}
class C extends B {
public void f(C param) { }
public void f(B param) { }
}
For each of the questions below, if there is no answer, simply write “no answer”. If there are multiple possible
answers, you may choose any answer.
(a) (3 points) Which of the methods above cannot be invoked using only the given variables? You are not
allowed to use the new keyword to create a new instance. Write your answer in the following format
ClassName::method(ParameterType) .
(b) (3 points) You are allowed to add the statement super.f(param); to one and only one of the meth-
ods, in only one of the classes. Which method should the statement super.f(param); be added to,
such that there is no answer for Part (a) above? Write your answer in the following format:
ClassName::method(ParameterType) .
(c) (3 points) Write a method invocation that will invoke the method A::f(C) . Your method invocation
can only use the given variables as the target of invocation and argument.
(d) (3 points) Write a method invocation that will cause a compilation error because there is no suitable
method that can be found. Your method invocation can only use the given variables as the target of
invocation and argument.
• a.f(a) leads to a compilation error since there is no method in class A that accepts A or its
super types as the argument.
• a.f(b) invokes C::f(B)
Page 13
Final Assessment CS2030S AY22/23 Sem 2
So, (a) the method A::f(B) did not get invoked. (b) To invoke A::f(B) , we can call super.f(param)
in B::f(B) , (c) b.f(c) would invoke A::f(C) , and (d) a.f(a) would cause a compilation error.
For (a) and (b), the grading is slightly more lenient.
• 3 marks for somwehat incorrect format (e.g., A::f(B param) , a::f(B param) , A::f(B param) ,
a.f(b) ) as long as the intention is clear.
• 1 mark for wrong method name as, technically, such a method does not exist (e.g., A::foo(B) ).
• 0 mark otherwise.
For (c) and (d), the grading is stricter because we require a method invocation.
• 3 marks for correct method invocation and allow a simple error (e.g., missing closing parenthesis)
* 0 mark for using a new variable for invocation (e.g., new B().f(c) ).
• 0 mark otherwise.
We allow the answer for (b) and (c) to be swapped. This assumes that students see the ordering in the
answer sheet as
+---+---+
| a | b |
+---+---+
| c | d |
+---+---+
instead of
+---+---+
| a | c |
+---+---+
| b | d |
+---+---+
Page 14
Final Assessment CS2030S AY22/23 Sem 2
16. (9 points) For each of the following code snippets, we will compile using the -Xlint:unchecked and -Xlint:rawtypes
flags.
(a) (3 points) Consider the following class and interface:
class A {
}
@FunctionalInterface
interface Producer {
int produce();
}
Explain why the Java compiler allows a to be cast into Producer without a compilation error and
without a compilation warning, even though it would cause a run-time error during execution.
Solution: Java allows type casting here because there is a possibility that during run-time, a is
an instance of a class that extends A and implements the Producer interface.
• Stating that A <: Producer or Producer <: A even though the code snippet in the ques-
tion already tells you otherwise.
• Vague statements like “(In the future), A can implement Producer ” to explain why it can
compile. Yes, you can make A implement Producer but that is not answering the question.
• Same explanation as the solution but using superclass of A instead of its subclass. This will
compile, but will also not cause a run-time error because you will then have A <: B <:
Producer .
• Talking about generics and raw types when there are no generics in the code snippet at all.
• Explicit type casting in the program does not mean that the code will always compile and/or
type casting is successful.
(b) (3 points) Now consider the code below. The difference from Part (a) is that Producer is now a generic
interface.
class A {
}
Page 15
Final Assessment CS2030S AY22/23 Sem 2
@FunctionalInterface
interface Producer<T> {
T produce();
}
Explain why the Java compiler produces a warning when casting a into Producer<Integer> .
Solution: During run-time, there is no way for the type checker to check that a is an instance of
Producer<Integer> since the type argument Integer has been erased.
• 3 marks for mentioning (i) checking that a is an instance of Producer<Integer> and (ii)
this check is not possible due to type erasure.
• 0 mark otherwise.
• Implementing produce method does not imply implementing the Produce interface. (con-
verse error, also common in 16a)
• Answers describe the process of type erasure, type casting etc. but make no reference to
what is happening in the question’s example.
• Talking about type erasure without explaining why type-checking cannot be done.
• Talking about type checking without type erasure. e.g., Student talks about invariance of
generics, such as Producer<String> not a subtype of Producer<Integer> . This does not
work without type erasure because it will just not compile.
• Many mistakes in 16a are also repeated here.
• Answering why the code compiles instead of why it gives a warning.
• Confusing what happens at compile time vs runtime. If it is just stating it incorrectly, marks
may still be given for some cases if the rest of the explanation is correct.
(c) (3 points) Now consider the code below. The difference from Part (a) is that class A is declared as
final .
final class A {
}
@FunctionalInterface
interface Producer {
int produce();
}
Explain why the Java compiler produces a compilation error. You may contrast this to your answer in
Part (a) of this question.
Page 16
Final Assessment CS2030S AY22/23 Sem 2
Solution: It is not possible that during run-time, a is an instance of a class that extends from
A , as A is declared as final. So the run-time type of a must be A and A does not implement
Producer .
• 3 marks for correct explanation that (i) A cannot be extended due to final , (ii) therefore
a must be have a run-time type of A , and since (iii) A ̸<: Producer<T> , (iv) it not possible
for run-time type of a to be a subtype of Producer<T> .
• 0 mark otherwise.
Note that (i) follows trivially from A being final and (iii) A ̸<: Producer follows trivially from
the code. Mentioning (i) and (iii) only is not sufficient.
About 9% got this correct. Common mistakes include
• Stopping only at the description of final and/or type relationship between A , Producer
and potential subtypes of A without talking about RTT of a .
• Confusing final keyword usage for classes with that of variables, talking about “immutabil-
ity” and “no reassignment/instantiation”.
• Assuming that explicit type cast implies a narrowing type conversion (talking about this is
not relevant anyway). This often results in students talking about how Producer ̸<: A
which does not make sense.
Page 17
Final Assessment CS2030S AY22/23 Sem 2
17. (6 points) Recall that the cs2030s.fp package includes the following Transformer and Combiner func-
tional interfaces.
@FunctionalInterface
interface Transformer<T, R> {
R transform(T t);
}
@FunctionalInterface
interface Combiner<S, T, R> {
R combine(S s, T t);
}
We want to create the class called Curry with two static methods. In the sample runs below, we have the
following code fragments.
Combiner<Integer, Integer, Integer> f1 = (x, y) -> x - y;
Transformer<Integer, Transformer<Integer, Integer>> f2 = x -> y -> x - y;
Answer the question by filling in the corresponding blanks on the answer sheet.
class Curry {
public static <S, T, R> __BLANK1__ curry(__BLANK2__ f) {
return __BLANK3__;
}
public static <S, T, R> __BLANK4__ uncurry(__BLANK5__ f) {
return __BLANK6__;
}
}
(a) (3 points) Implement a static method curry that takes in a Combiner and returns a Transformer
satisfying the following sample run.
jshell> f1.combine(2,1)
$.. ==> 1
jshell> Curry.curry(f1).transform(2).transform(1)
$.. ==> 1
(b) (3 points) Implement a static method uncurry that takes in a Transformer and returns a Combiner
satisfying the following sample run.
jshell> f2.transform(2).transform(1)
$.. ==> 1
jshell> Curry.uncurry(f2).combine(2,1)
$.. ==> 1
Solution:
class Curry {
public static <S,T,R> Transformer<S, Transformer<T, R>> curry(Combiner<S, T, R> f) {
return x -> y -> f.combine(x, y);
}
public static <S,T,R> Combiner<S,T,R> uncurry(Transformer<S, Transformer<T, R>> f) {
return (x, y) -> f.transform(x).transform(y);
}
}
Note that PECS is not needed if we let only type inference. We allow three alternatives for BLANK1 / BLANK5
and two alternatives for BLANK2 / BLANK4 , with PECS considered (left as an exercise to the reader).
Page 18
Final Assessment CS2030S AY22/23 Sem 2
Page 19
Final Assessment CS2030S AY22/23 Sem 2
18. (15 points) Consider the following implementation of InfiniteList , taken from the notes:
class InfiniteList<T> {
public final Producer<T> head;
public final Producer<InfiniteList<T>> tail;
public T head() {
T h = this.head.produce();
return h == null ? this.tail.produce().head() : h;
}
@FunctionalInterface
interface BooleanCondition<T> {
boolean test(T t);
}
@FunctionalInterface
interface Transformer<T, R> {
R transform(T t);
}
Page 20
Final Assessment CS2030S AY22/23 Sem 2
(a) (4 points) Implement a method called lazyGet inside the InfiniteList class that takes in an int
index i and lazily returns the i-th element from the list as a producer of the element.
The method is declared as:
public Producer<T> lazyGet(int i) {
}
and can be used as follows:
jshell> Transformer<Integer, Integer> f = x -> {
...> System.out.println(" > " + (x + 2));
...> return x + 2;
...> }
jshell> Producer<Integer> p = InfiniteList.iterate(0, f).lazyGet(0)
jshell> p.produce()
$.. ==> 0
jshell> Producer<Integer> p = InfiniteList.iterate(0, f).lazyGet(2)
jshell> p.produce()
> 2
> 4
$.. ==> 4
Fill in the body of the method lazyGet .
Solution:
• When i == 0
– 2 marks for () -> head()
– 1 mark is given if the code returns either head or () -> head.produce() instead,
as this might return a producer that produces null .
– 0 mark for () -> head , as this returns Producer<Producler<T>> .
• When i > 0 ,
– 2 marks for () -> tail().lazyGet(i-1).produce())
– 1 mark for () -> tail.produce().lazyGet(i-1).produce() as this includes filtered
elements when counting down from i .
– 0 mark for () -> tail().lazyGet(i-1) , as this returns Producer<Producer<T>> .
– Other common wrong combinations (0 marks) include () -> tail.lazyGet(i-1) ,
() -> tail.produce().lazyGet(i-1) , etc.
(b) (6 points) Write a method called skip inside the InfiniteList class with a parameter x of type
T . The method skips all beginning elements that are equal to x and returns the remaining elements
as an infinite list. Checking for equality should be done using the equals method.
For instance, calling skip(0) on the infinite list 0, 0, 0, 1, 1, 1, 2, 2, 2, . . . returns the infinite list 1, 1, 1,
2, 2, 2 . . .. Calling skip(1) on the infinite list 0, 0, 0, 1, 1, 1, 2, 2, 2, . . . returns 0, 0, 0, 1, 1, 1, 2, 2, 2, . . ..
The method skip has the following declaration and should be executed eagerly.
Page 21
Final Assessment CS2030S AY22/23 Sem 2
}
Fill in the body of the method skip .
Solution:
(c) (5 points) Using the method skip above, write a method called unique inside the InfiniteList
class that lazily returns a new infinite list where consecutive elements that are the same are removed.
For instance, calling unique() on the infinite list 0, 0, 1, 1, 1, 0, 0, 1, 1, 2, 2, . . ., returns 0, 1, 0, 1, 2, . . ..
The method unique has the following declaration and should be executed lazily.
public InfiniteList<T> unique() {
}
Fill in the body of the method unique .
Solution:
• 1 marks for the first argument. Either head or () -> head() receive 1 marks. () -> head
receives 0.
• 4 marks for the second argument. 4 marks are given for
Page 22
Final Assessment CS2030S AY22/23 Sem 2
– () -> skip(head()).unique()
– () -> tail().skip(head()).unique()
– () -> tail.produce().skip(head()).unique()
– () -> tail().unique().skip(head())
– () -> tail.produce().unique().skip(head())
Page 23
Final Assessment CS2030S AY22/23 Sem 2
19. (6 points) Consider a monad called Monad<X> that adds a context to the computation on a value of type
X . We have the following methods em , fm , gm , and hm that take in a value of type X and return a
Monad<X> . We wish to apply the four methods, in order, to a value x , like so:
When you asked an AI to write a program snippet to chain the methods together, the following program is
generated:
Monad<X> efm(X x) {
return Monad.of(x)
.flatMap(i -> em(i));
.flatMap(i -> fm(i));
}
Monad<X> ghm(X x) {
return Monad.of(x)
.flatMap(i -> gm(i))
.flatMap(i -> hm(i))
}
The AI insists that the two programs are equivalent but refuses to prove it to you. So you set out to prove it
yourselves. Fill in the blanks below to show that the two expressions are equivalent:
We start with:
Monad.of(x)
.flatMap(i -> efm(i))
.flatMap(i -> ghm(i))
And so, the AI is correct and the two programs are equivalent.
Solution:
Monad.of(x)
.flatMap(i -> Monad.of(i).flatMap(i -> em(i)).flatMap(i -> fm(i)))
.flatMap(i -> Monad.of(i).flatMap(i -> gm(i)).flatMap(i -> hm(i)))
Page 24
Final Assessment CS2030S AY22/23 Sem 2
Each blank must be a single expression with some minor syntactic error allowed:
• We do not accept expressions where misplaced parenthesis makes it ambiguous in case of asso-
ciativity or other parentheses’ precedence.
• We do not accept non-expression such as below:
– Monad.of(i).flatMap(i -> em(i))).flatMap(i -> fm(i)
– It does not into the blank as it closes that already given parentheses prematurely and ex-
tends it with additional flatMap .
Page 25
Final Assessment CS2030S AY22/23 Sem 2
Returns a stream consisting of the elements of this stream that match the given predicate.
map
Returns a stream consisting of the results of applying the given function to the elements of this stream.
flatMap
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream
produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents
have been placed into this stream. (If a mapped stream is null an empty stream is used, instead.)
limit
Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length.
forEach
reduce
Performs a reduction on the elements of this stream, using the provided identity value and an associative accumulation
function, and returns the reduced value.
of
Returns a sequential ordered stream whose elements are the specified values.
iterate
Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element
seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc.
END OF PAPER
Page 26