Final 2122 s2 W Solution
Final 2122 s2 W Solution
SCHOOL OF COMPUTING
FINAL ASSESSMENT FOR
Semester 2 AY2021/2022
INSTRUCTIONS TO CANDIDATES
1. This assessment paper contains 30 questions and comprises 19 printed pages, including this page.
2. Write all your answers in the answer sheet provided.
3. The total marks for this assessment is 90. Answer ALL questions.
4. This is a OPEN BOOK assessment. You are only allowed to refer to hard copy materials.
Page 2
Final Assessment CS2030S AY21/22 Sem 2
Solution: The program does not compile. Whether a program will terminate or get into an infinite
loop does not stop javac from compiling. The tow method can be called on any object of type
Railcar (or its subtypes). The railcar class is abstract and cannot be instantiated and therefore D
is the correct option.
Solution: This question involves some simple tracing through the code. The important thing to note
is that both Carriage ’s and TrainEngine ’s count towards train length. The answer is 4.
3. (1 point) Given the above code excerpt, consider the following line:
double weight = engine.getTotalWeight();
Solution: This question involves some simple tracing through the code excerpt. The important thing
to note is that carriage4 is not attached to the train. The answer is 400.5.
4. (2 points) Do the classes TrainEngine and Carriage follow the principle of Tell, Don’t Ask? If not,
why not?
A. Does follow the principle.
Page 3
Final Assessment CS2030S AY21/22 Sem 2
B. Does not follow the principle, as TrainEngine gets information directly from Carriage .
C. Does not follow the principle, as Carriage has no getters or setters.
D. Does not follow the principle, as TrainEngine has no getters or setters.
E. Does not follow the principle, as Railcar is abstract.
Solution: Here we need to check where TrainEngine and Carriage interact with each other,
namely the getTotalWeight method. We can see here that TrainEngine is asking for the weightPerPackage
and numberOfPackages fields to calculate the carriage weight, rather than telling the carriage to cal-
culate its own weight. The presence of setters and getters does not necessarily indicate whether the
Tell-Don’t-Ask principle is being followed. Railcar being abstract is immaterial to the question. There-
fore, the answer is B.
5. (2 points) In the getTotalWeight method, there are two typecasts. These are examples of:
A. Type erasure
B. Type inference
C. Narrowing type conversion
D. Widening type conversion
E. Generic types
Solution: No generic types are used and therefore it does not have anything to do with Type erasure,
Type inference, or Generic types. Therefore we must consider which type of conversion it is, either
narrowing or widening. We can see that it is casting from a more general type to a more specific one,
therefore this is a narrowing type conversion and this option is C. Note that widening type conversion
is implicit in Java (i.e. you do not need to cast).
6. (2 points) The classes above were designed based on the following specification:
“A train consists of multiple railcars connected in a line. The first railcar should be a train engine, and this
can be connected to either a carriage or another train engine. Your program must be able to calculate the
train length and the total carrying weight of the train. The total carrying weight of a train is the weight of
all packages in all of the connected carriages.”
Do the provided classes Railcar , TrainEngine , and Carriage meet all aspects of the above specifi-
cation? Why or why not? Choose the most correct answer.
A. The code meets the specification. This is because the programmer correctly used OOP principles when
designing their code.
B. The code meets the specification. This is because all trains consist of both train engines and carriages,
and the train length and carrying weight can be calculated correctly.
C. The code does not meet the specification. This is because while it is possible to create a train with
both train engines and carriages, it is not possible to calculate the total carrying weight as this method
assumes all remaining railcars are carriages.
D. The code does not meet the specification. This is because trains can only contain carriages and engines
are not allowed to be at the front of a train.
E. The code does not meet the specification. This is because trains can be infinite in length and therefore
the program will never terminate, and it is never possible to calculate the train length.
F. The code does not meet the specification. This is because no train tracks or railway stations have been
implemented, as such the trains can not move.
Page 4
Final Assessment CS2030S AY21/22 Sem 2
Solution: This question requires a deeper reading of the program and excerpt above. We can note
that the method getTotalWeight has an implicit assumption that the runtime type of the returned
object of getTotalWeight will be of type Carriage , and therefore it is safe to cast. This is not true
in the specification, as a train can have many TrainEngine ’s (not just the one at the front). Therefore
the answer is C.
Page 5
Final Assessment CS2030S AY21/22 Sem 2
For this section, you will need to refer to the following classes and interfaces:
public T produceNew() {
return producer.produce();
}
public static <S extends Product> Factory<S> newFactory(Producer<? extends S> producer) {
return new Factory<>(producer);
}
}
Page 6
Final Assessment CS2030S AY21/22 Sem 2
Solution: This question involves looking through the class and interface structure and determining all
of the subtyping relationships between classes/interfaces. This question also tests your understanding
of the transitivity and reflexivity of subtype relationships. The answer is therefore A, D, E, F, and H.
Will this code excerpt compile? If so what will be printed out? If not, why not?
A. Compiles. Will print “Repairing”.
B. Compiles. Will print “Repairing Laptop”.
C. Compiles. Will print “Repairing Computer”.
D. Does not compile. This is because the factory computerFactory can not be instantiated due to a
type conflict.
E. Does not compile. This is because the return type of computerFactory::produceNew is not com-
patible with the variable product due to a type conflict.
Solution: This code excerpt will compile. The factory produces new Computer objects using Computer::new .
As a result, on calling the repair method, “Repairing Computer” will be printed. Therefore the an-
swer is C.
Will this code excerpt compile? If so what will be printed out? If not, why not?
A. Compiles. Will print “Repairing”.
B. Compiles. Will print “Repairing Laptop”.
C. Compiles. Will print “Repairing Computer”.
D. Does not compile. This is because the factory computerFactory can not be instantiated due to a
type conflict.
Page 7
Final Assessment CS2030S AY21/22 Sem 2
E. Does not compile. This is because the return type of computerFactory::produceNew is not com-
patible with the variable computer due to a type conflict.
Solution: This code excerpt will not compile. We can see that the first line will compile as the Factory::new
uses the upper bounded wildcard, and it is possible to assign the variable to this newly created factory
object. However, the second line will not compile as the return type of computerFactory::produceNew
is Product which is the supertype of Computer . Therefore the answer is E.
10. (1 point) After type erasure, what will be the compile-time type of the field producer in the class Factory<T> ?
A. Object
B. T
C. Producer
D. Producer<T>
E. ? extends T
Solution: After type erasure, all wildcards and generic type parameters will be erased, leaving only
Producer . Therefore the answer is C.
11. (2 points) A general factory which produces a product of type A can be repurposed to produce products
of type B , but only if B <: A .
Consider the following code excerpt:
Factory<Computer> computerFactory = Factory.newFactory(Computer::new);
computerFactory.changeProduction(Laptop::new);
What is the compile-time type before type erasure of computerFactory after this code excerpt is exe-
cuted?
A. Factory<Computer>
B. Factory<Laptop>
C. Factory<Object>
D. Factory<Product>
E. Factory<Car>
Solution: The key to understanding this question is to realize that the compile-time type (before type
erasure) of computerFactory will not change even if we change the “type” the factory is producing
because Java is statically typed. Therefore it remains Factory<Computer> , and the answer is A.
12. (1 point) What is the compile-time return type of computerFactory.produceNew(); after the above
code excerpt is executed?
A. Object
B. Product
C. Computer
Page 8
Final Assessment CS2030S AY21/22 Sem 2
D. Laptop
E. Factory
13. (1 point) What is the run-time return type of computerFactory.produceNew(); after the above code
excerpt is executed?
A. Object
B. Product
C. Computer
D. Laptop
E. Factory
Solution: However, the run-time type can change, and in this case, it changes to Laptop . Therefore
the answer is D.
Page 9
Final Assessment CS2030S AY21/22 Sem 2
A. Product
B. Computer
C. Laptop
D. Object
E. No type is inferred
Solution: To answer this question we must look at the constraints on the type parameter T . First, we
know that T is a bounded type parameter and therefore we know that T <: Product .
Secondly, we know that the return type of the method fun is of type T and it is constrained by the
type of variable p , therefore we know that T <: Product .
Finally, we know that the object passed to the argument of the method fun also constrains T . Specif-
ically, we know that Factory<Computer> <: Factory<? extends T> , which means that T >:
Computer . We now know that Computer <: T <: Product . We take the most specific type which
is Computer .
Therefore the answer is B.
A. Product
B. Computer
C. Laptop
D. Object
E. No type is inferred
Solution: We follow the same procedure as the previous question. Firstly, we know that T is a bounded
type parameter and therefore we know that T <: Product .
Secondly, we know that the return type of the method fun is of type T and it is constrained by the
type of variable p , therefore we know that T <: Laptop .
Page 10
Final Assessment CS2030S AY21/22 Sem 2
Finally, we know that the object passed to the argument of the method fun also constrains T . Specif-
ically, we know that Factory<Product> <: Factory<? extends T> , which means that T >:
Product . We now know that T >: Product and T <: Laptop . There is no type parameter that
can satisfy these constraints.
Therefore the answer is E.
Page 11
Final Assessment CS2030S AY21/22 Sem 2
@FunctionalInterface
public interface Combiner<S, T, R> {
R combine(S s, T t);
}
Solution: The main defining property of functional interfaces is that they have a single abstract method.
Therefore the answer is D.
Write the equivalent anonymous class of the above lambda function. You may omit the declaration of the
variable isDivisible in your answer.
Solution:
18. (1 point) If we were to implement a curried version of the above lambda function what would the compile-
time type of isDivisible be?
Page 12
Final Assessment CS2030S AY21/22 Sem 2
Solution: Our Combiner<Integer, Integer, Boolean> will become a chain of two unary func-
tions (two Transformer s). Therefore the answer is E.
m1 = 0.4;
m2 = 0.6;
Will the code excerpt compile? If it compiles, what will the value of secondSum be? If it does not compile,
why not?
Solution: The variables m1 and m2 are captured by weightedSum . Therefore they can not be
changed and must be effectively final. In this code excerpt, m1 and m2 are changed and as such
it will not compile.
Therefore the answer is E.
Page 13
Final Assessment CS2030S AY21/22 Sem 2
Stream.iterate(1, i -> i + 1)
.map(x -> x * 10)
.filter(x -> x > 50);
What will be the elements in the resulting stream above when it is eventually evaluated? If this Stream is
infinite, write the first 5 elements, followed by “...”. If it is a finite stream, write out all the elements of the
stream.
Stream.iterate(1, i -> i + 1)
will give the following stream: 1, 2, 3, 4, 5, . . ..
Stream.iterate(1, i -> i + 1).map(x -> x * 10)
will give the following stream: 10, 20, 30, 40, 50, . . ..
Finally,
Stream.iterate(1, i -> i + 1).map(x -> x * 10).filter(x -> x > 50)
will give 60, 70, 80, 90, 100, . . ..
Stream.iterate(1, i -> i + 1)
.map(n -> Stream.iterate(1, i -> i + 1).limit(n).reduce(0, (x, y) -> x + y))
.limit(6);
What will be the elements in the resulting stream above when it is eventually evaluated? If this Stream is
infinite, write the first 5 elements, followed by “...”. If it is a finite stream, write out all the elements of the
stream.
Page 14
Final Assessment CS2030S AY21/22 Sem 2
Solution: We will break this down by method call, and we will start with the lambda passed to map :
n -> Stream.iterate(1, i -> i + 1).limit(n)
For a given n, we can see that we will get a stream containing the numbers from 1 to n.
For example, for n = 1, we will get a stream containing 1, and for n = 4 we will get a stream containing
1, 2, 3, and 4.
Now we add back in the reduce method:
n -> Stream.iterate(1, i -> i + 1).limit(n).reduce(0, (x, y) -> x + y)
This will return the sum of all the elements in the stream. For example, for n = 1, we will get the sum
1, and for n = 4 we will get the sum 10.
We can now go back into the original map and limit method:
Stream.iterate(1, i -> i + 1)
.map(n -> Stream.iterate(1, i -> i + 1).limit(n).reduce(0, (x, y) -> x + y))
.limit(6);
We can see that this will create a finite stream with the following six elements: 1, 3, 6, 10, 15, 21.
Page 15
Final Assessment CS2030S AY21/22 Sem 2
Which of the following single line CompletableFutures pipelines will compile and give the same result
as the above code excerpt?
A. int i = CompletableFuture.supplyAsync(() -> zero())
.thenAcceptAsync(x -> addOne(x))
.thenAcceptAsync(x -> timesTwo(x)).join();
B. int i = CompletableFuture.supplyAsync(() -> zero())
.thenApply(x -> addOne(x))
.thenApply(x -> timesTwo(x)).join();
C. int i = CompletableFuture.supplyAsync(() -> zero())
.thenApplyAsync(x -> addOne(x))
.thenApplyAsync(x -> timesTwo(x)).join();
D. int i = CompletableFuture.supplyAsync(() -> timesTwo(addOne(zero()))).join();
E. int i = CompletableFuture.supplyAsync(() -> zero())
.thenApply(x -> timesTwo(x))
.thenApply(x -> addOne(x)).join();
Page 16
Final Assessment CS2030S AY21/22 Sem 2
Compare the time taken to run the code excerpts above, focusing on the delay caused by slowTask()
and ignore any other threading or computational overhead. We assume that an infinite number of worker
threads are available, and the system can run at maximum concurrency.
Which of the following statement is correct?
A. I and II take the same amount of time; III is one second faster than I and II.
B. II and III take the same amount of time; I is one second faster than II and III.
C. I and III take the same amount of time; II is one second slower than I and III.
D. I is one second faster than III; III is one second faster than II.
E. I, II, and III take the same amount of time.
F. None of the above.
So it takes 3 seconds. thenApplyAsync causes the two tasks to run in parallel (since we assumed
there are an infinite number of workers)
In Excerpt II, we have two thenApply calls so the addOne will be executed by the same thread that
executes zero . We have
cf = CompetableFuture.supplyAsync(lambda1);
cf.thenApply(lambda2);
cf.thenApply(lambda3);
cf.thenApply(lambda4);
Then after lambda1 runs, lambda4 will run first, followed by lambda3 , then lambda2 .
In Excerpt III, we have thenApplyAsync followed by thenApply . So the order is
Page 17
Final Assessment CS2030S AY21/22 Sem 2
It still takes four seconds, as the second asynchronous addOne is not offered to another worker to run
until the first addOne completes.
The answer of this question is B.
If we initialize cf2 with thenApply and cf3 with thenApplyAsync instead, then the asyn-
chronous addOne is offered to another worker first. Then the original worker works on the second
addOne . In this case, we have
Page 18
Final Assessment CS2030S AY21/22 Sem 2
FlatMapCount.of(2).flatMap(mapper1);
// returns [3, 2] (does not pass)
mapper1.transform(2);
// returns [3, 1] (does not pass)
Page 19
Final Assessment CS2030S AY21/22 Sem 2
monad; returns
FlatMapCount.of(2).flatMap(mapper1).flatMap(mapper2);
// returns [6, 4] (passes)
FlatMapCount.of(2).flatMap(x -> mapper1.transform(x).flatMap(mapper2));
// returns [6, 4] (passes)
Page 20
Final Assessment CS2030S AY21/22 Sem 2
Consider the following Lazy class. This class is similar to the one you implemented except that it does not
use the Maybe class.
public class Lazy<T> {
private Producer<T> producer;
private T value;
private boolean evaluated;
private Lazy(Producer<T> s) {
this.producer = s;
this.evaluated = false;
this.value = null;
}
public T get() {
if (!this.evaluated) {
this.value = this.producer.produce();
this.evaluated = true;
}
return this.value;
}
}
On the provided sheet, draw the stack and the heap when the program gets to line A. Include all objects that
are created.
You may assume that the code excerpt is being run in the main method. You may ignore any variables
used in main but not shown in the excerpt above.
Solution:
Page 21
Final Assessment CS2030S AY21/22 Sem 2
Stack Heap
Lazy
evaluated true
y:
value 1 6
producer
mapper
this
Lazy
main evaluated true
newValue
y:
value 1 3
lazyInteger producer
Anon. Producer
result 6 () -> 3
Page 22
Final Assessment CS2030S AY21/22 Sem 2
public T head() {
return this.head.get();
}
This is a simplified version of InfiniteList that does not provide a filter method, and therefore it
does not need to use the Maybe class. Furthermore, we have simplified the type of the method parameters
by not applying PECS.
In this question, you will need to implement three methods for InfiniteList .
Page 23
Final Assessment CS2030S AY21/22 Sem 2
Solution: This section aims to assess students on a variety of concepts. The most important ones are
(i) lambda expression, (ii) lazy evaluation, (iii) infinite list, and (iv) type matching.
In general, an answer is classified into three categories: wrong (0 marks); partially correct (half of the
maximum marks); almost correct/correct (full marks).
Any answer that eagerly evaluates the list receives 0 marks. These include answers that call tail()
or head() .
Any answer with an egregious type mismatch receives 0 marks. These include many answers that pass
in a producer to the constructors of the infinite list.
Students are expected to be able to determine the type of an expression. Common mistakes include try-
ing to call methods of Lazy (e.g., map ) on an InfiniteList or a T ; or call methods of InfiniteList
(e.g., append ) on a Lazy .
Another common mistake is to redundantly call the factory method or constructor of Lazy . The
head and tail of the InfiniteList are already Lazy objects, so that is no need the “re-wrap”
the value of type T or the InfiniteList in a Lazy instance. For instance, Lazy.of(() -> head())
is equivalent to head .
28. (4 points) First, implement the append method to concatenate a given infinite list to the current list.
Consider the following examples of use:
InfiniteList<Double> l1 = InfiniteList.iterate(1.0, x -> x + 1).limit(2);
l1.toList(); // returns [1.0, 2.0]
InfiniteList<Double> l3 = l1.append(l2);
l3.limit(6).toList(); // returns [1.0, 2.0, 0.1, 0.2, 0.3, 0.4]
Reminder: l3 should be lazily evaluated so that the values shown should be produced only on demand.
Note that it is possible for l1 to be a list with an infinite number of elements (not truncated with limit(2) )
and therefore we may not see any elements from l2 .
Implement the append method below. You may assume the Sentinel::append method is correctly
implemented.
public InfiniteList<T> append(InfiniteList<T> list) {
return new InfiniteList<T>(???, ???);
}
Solution:
Some students included checks for this being a sentinel. This check is not necessary, as dynamic
binding would cause the Sentinel::append to be called and handle this special case for us.
Page 24
Final Assessment CS2030S AY21/22 Sem 2
29. (6 points) Now, implement the zipWith method to combine two lists, element-wise, with a lambda ex-
pression.
Consider the following examples of use:
InfiniteList<Integer> l1 = InfiniteList.iterate(1, x -> x + 1);
l1.limit(4).toList(); // returns [1, 2, 3, 4]
Remember: l3 should be lazily evaluated so that the values shown should be produced only on demand.
The output of zipWith will have the length equivalent to the shorter of the two lists provided.
Implement the zipWith method below. You may assume the Sentinel::zipWith method is correctly
implemented.
public <U,R> InfiniteList<R> zipWith(InfiniteList<U> list,
Transformer<T, Transformer<U, R>> transform) {
return new InfiniteList<R>(???, ???);
}
Solution:
This question additionally assesses if students know how to invoke a curried function. As such, students
who treated the curried lambda as a lambda expression with two parameters (e.g., BiFunction or
Combiner ) would get 0 marks.
An alternative to the first argument is
Note that there is an implicit assumption that the given list is not a sentinel. Otherwise, the answer
cannot neatly fit into the given code template. Any code that handles the special case where list is
sentinel is ignored during grading and is not penalized.
30. (8 points) Now, we will finally make our InfiniteList class into a monad by implementing the flatMap
method.
Consider the following examples of use:
InfiniteList<Integer> l1 = InfiniteList.iterate(1, x -> x + 1);
l1.limit(4).toList(); // returns [1, 2, 3, 4]
Page 25
Final Assessment CS2030S AY21/22 Sem 2
Transformer<Integer, InfiniteList<Integer>> t;
t = x -> InfiniteList.iterate(100 * x, y -> y + x).limit(2);
InfiniteList<Integer> l2 = l1.flatMap(t);
Reminder: l2 should be lazily evaluated so that the values shown should be produced only on demand.
It is possible for the lambda expression passed into flatMap to produce an infinite list. In which case, we
will never see an element from l2 produced by the second element of l1 .
Implement the flatMap method below. You may assume the Sentinel::flatMap method is correctly
implemented.
public <R> InfiniteList<R> flatMap(Transformer<T, InfiniteList<R>> transformer) {
Lazy<InfiniteList<R>> list = this.head.map(transformer);
return new InfiniteList<R>(???, ???);
}
Solution:
Note that there is an implicit assumption that list is not a sentinel. Otherwise, the answer cannot
neatly fit into the given code template. Any code that handles the special case where list is sentinel
is ignored during grading and is not penalized.
END OF PAPER
Page 26