Ex 7: To Infinity and Beyond
Basic Information
- Deadline: 29 October 2024, Tuesday, 23:59 SGT
- Difficulty: ★★★★★★
Prerequisite
- Caught up to Unit 33 of Lecture Notes.
- Familiar with CS2030S Java style guide.
Class Files
If you have not finished Programming Exercise 5 and 6, do not worry.
We provide .class
files for the functional interfaces as well as Maybe<T>
and Lazy<T>
.
Note that the implementation for Maybe<T>
is badly written not following OOP but it is the correct implementation.
Additionally, the class files were compiled on PE node using Java 21 compiled. If you are not using Java 21 or if you are not working on PE node, you may get different result. It is unlikely, but the possibility is there. Please only do your work on the PE node.
You are required to use the given .class
files as we will be using our version during testing.
In other words, you are not allowed to add methods not specified by Ex 5 and Ex 6.
Maybe Class
The class Maybe<T>
has the following public
methods. You cannot use any methods that are not public
from outside the package.
Method | Description |
---|---|
static <T> Maybe<T> of(T val) |
Creates a Maybe<T> with the given content val if val is not null . Otherwise, returns the shared instance of None<?> . |
static <T> Maybe<T> some(T val) |
Creates a Maybe<T> with the given content val which may be null . |
static <T> Maybe<T> none() |
Creates a Maybe<T> without any content, this is guaranteed to return the shared instance of None<?> . |
String toString() |
Returns the string representation of Maybe<T> . |
boolean equals(Object obj) |
|
<U> Maybe<U> map (Transformer<? super T, ? extends U> fn) |
|
Maybe<T> filter (BooleanCondition<? super T> pred) |
|
<U> Maybe<U> flatmap (Transformer<? super T, ? extends Maybe<? extends U>> fn) |
|
T orElse(Producer<? extends T> prod) |
|
void ifPresent(Consumer<? super T> cons) |
|
Lazy Class
The class Lazy<T>
has the following public
methods. You cannot use any methods that are not public
from outside the package.
Method | Description |
---|---|
static <T> Lazy<T> of(T val) |
Creates a Lazy<T> with the given content val already evaluated. |
static <T> Lazy<T> of(Producer<? extends T> prod) |
Creates a Lazy<T> with the content not yet evaluated and will be evaluated using the given producer. |
boolean equals(Object obj) |
Returns true if the content is equal to the content of obj . Otherwise returns false . This forces evaluation of the content. |
<U> Lazy<U> map (Transformer<? super T, ? extends U> fn) |
Lazily maps the content using the given transformer. |
Lazy<Boolean> filter (BooleanCondition<? super T> pred) |
Lazily test if the value passes the test or not and returns a Lazy<Boolean> to indicate the result. |
<U> Lazy<U> flatMap (Transformer<? super T, ? extends Lazy<? extends U>> fn) |
Lazily creates a new instance of Lazy<T> by applying the transformer fn to the content without wrapping it in another Lazy<..> . |
<U, V> Lazy<V> combine (Lazy<? extends U> lazy, Combiner<? super T, ? super U, ? extends V> fn) |
Combine this with lazy using Combiner by invoking fn.combine(this.get(), lazy.get()) . Then we wrap the result back in Lazy . |
T get() |
Evaluates (if not yet evaluated, otherwise do not evaluate again) and returns the content. |
Infinity
This is a follow-up from Ex 6. In Ex 6, we have constructed a generic class Lazy<T>
using Maybe<T>
. Now we are going to combine them into a Lazy<Maybe<T>>
to build an infinite list. We need the Lazy<..>
because we want our infinite list to be lazily evaluated. We need the Maybe<T>
because the value may be present or may be missing due to filter
. Recap that in the lecture notes we use the null
value to indicate missing values because:
- We need a value that all possible generic type
T
has. - We need a value that indicates a value should not be included.
The only solution was to use null
because there is no other value that satisfies these two conditions. Of course, it will be better if we have a second null
value (maybe we call it None
like in Python, heeeyyy wait a minute, that's our None<T>
) to indicate this, but unfortunately we do not have such value. That is why we need to use Maybe<T>
.
Please make sure you are familiar with Maybe<T>
and Lazy<T>
before proceeding. You do not have to know the implementation but you should understand the expected behavior.
Constraints
We will recap some of the constraints for our labs so far.
- You are NOT allowed to use raw types.
@SuppressWarnings
must be used responsibly, in the smallest scope, and commented.
Additionally, you have the following constraints for this lab.
- You are NOT allowed to use
java.util.stream.Stream
. - You are NOT allowed to add new classes (nested or otherwise).
- You are NOT allowed to add new methods in the
InfiniteList
(not even private methods). - You are NOT allowed to use conditional statement (e.g.,
if
-else
) or conditional expression (e.g.,?:
operator).- Unless otherwise stated.
- You are NOT allowed to use loops (e.g.,
while
-loop orfor
-loop).- You may, however, use recursion (but possibly without conditionals).
- There must only be a single instance of
Sentinel
.
Relaxation
As a relaxtaion, the type signature in the templates are already the most flexible types. You have suffered enough thinking about more flexible type in the past two labs. The focus here is about laziness.
Basic
You are already given most of the implementations including head()
, tail()
, map(..)
, and filter(..)
. Please study them carefully. Additionally, to help with debugging, a toString()
has already been provided for you. Lastly, there are three factory methods for InfiniteList
, namely generate(..)
, iterate(..)
, and sentinel()
. The last one creates an empty list.
As we also want to limit our infinite list to a potentially finite list, we have provided the Sentinel
class. This class is rather straightforward as it overrides all of the methods in InfiniteList<T>
. However, in most cases, it simply returns itself (through the use of InfiniteList.<Object>sentinel()
) or throw an exception.
You can test the initial implementation by running Test0A.java
to Test0C.java
.
1 2 3 4 5 6 7 |
|
Tasks
Time Complexity
In all of the tasks below, you do not have to worry too much about time complexity especially if you are taking or have taken CS2040. There may not be an \(O(n)\) solution and you may have to find an \(O(n^2)\) solution instead.
Task 1: Limit
The Sentinel
class is not only an indication of an empty list but because our idea of an InfiniteList
is the value (or value wrapped in Lazy
and Maybe
, i.e., the head) and the rest of the list (which is another InfiniteList
), an empty list is also an indicator for the end of the list. Given a Sentinel
, we can now write two methods:
- Implement the method
InfiniteList<T> limit(long n)
inInfiniteList
.- The method takes in a number
n
. - The method returns a new
InfiniteList
that is lazily computed which is the truncation of theInfiniteList
to a finite list with at mostn
elements. - The method should not count the elements that are filtered out by
filter
(if any). - The method is allowed to use conditional statement/expression.
- The method takes in a number
- Override the method
InfiniteList<T> limit(long n)
inSentinel
.- The method takes in a number
n
. - Determine the appropriate behaviour for this.
- The method takes in a number
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test1.java | |
---|---|
1 2 3 |
|
Task 2: To List
- Implement the method
List<T> toList()
inInfiniteList
.- The method takes in no parameter.
- The method returns a new
List<T>
(should really just beArrayList<T>
) which is a collection elements of theInfiniteList
in the same order as they appear in theInfiniteList
.
- Override the method
InfiniteList<T> toList()
inSentinel
.- The method takes in no parameter.
- Determine the appropriate behaviour for this.
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test2.java | |
---|---|
1 2 3 |
|
Task 3: It Takes a While
Now we want to implement the takeWhile
method.
- Implement the method
InfiniteList<T> takeWhile(BooleanCondition<? super T> pred)
inInfiniteList
.- The method takes in a
BooleanCondition
. - The method returns an
InfiniteList
that is a truncated version of the initialInfiniteList
.- The method truncates the infinite list as soon as it finds an element that evaluates the condition to
false
.
- The method truncates the infinite list as soon as it finds an element that evaluates the condition to
- The method is allowed to use conditional statement/expression.
- The method takes in a
- Override the method
InfiniteList<T> takeWhile(BooleanCondition<? super T> pred)
inSentinel
.- The method takes in a
BooleanCondition
. - Determine the appropriate behaviour for this.
- The method takes in a
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test3.java | |
---|---|
1 2 3 |
|
Task 4: Folding it Right
In the lecture, we discuss about the behaviour of reduce
method of Stream
. reduce
is actually equivalent to fold left. In a fold left, given a list [v1, v2, v3, v4]
, an initial value e
and the operation +
, the operation performed is as follows:
(((e + v1) + v2) + v3) + v4
What we want is the opposite. We want to do a fold but from the right. In a fold left, given a list [v1, v2, v3, v4]
, an initial value e
and the operation +
, the operation performed is as follows:
(v1 + (v2 + (v3 + (v4 + e))))
Since the order of operations are different, the result may potentially be different. For +
, these two are going to produce the same result. But for -
, the result will be different. Note that this is a terminal operation in Java stream.
- Implement the method
<U> U foldRight(U id, Combiner<? super T, U, U> acc)
inInfiniteList
.- The method takes in the initial value
id
and an accumulatoracc
.- Note that
Combiner<T, S, R>
is a new interface with the implementation given incs2030s/fp/Combiner.java
. - It has a single abstract method
R combine(T arg1, S arg2)
.
- Note that
- The method returns the result of fold right of type
U
performed on theInfiniteList
with the given accumulatoracc
starting from the initial valueid
.- Note that the name
id
is used as typically the initial value is the identity operation of the accumulator.
- Note that the name
- The method takes in the initial value
- Override the method
<U> U foldRight(U id, Combiner<? super T, U, U> acc)
inSentinel
.- The method takes in the initial value
id
and an accumulatoracc
. - Determine the appropriate behaviour for this.
- The method takes in the initial value
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test4.java | |
---|---|
1 2 3 |
|
Task 5: Documenting Your Code
Now that we are beginning to build our own package that others can use, we should start to produce documentation on our code.
From Ex 7 onwards, you are required to document your classes and methods with Javadoc comments. You have seen examples from the skeleton code earlier exercises. For more details, see the JavaDoc guide. The checkstyle tool now checks for JavaDoc-related style as well.
For Ex 7, you should write javadoc documentation for all methods in InfiniteList.java
. Documenting the code your wrote previously for Ex 5 and Ex 6 are encouraged but optional.
Your task is to document the remaining methods. We have provided some documentations on some of the codes as example. You should also double-check that the provided documentations satisfies the style guide.
JavaDoc | |
---|---|
1 2 |
|
Following CS2030S Style Guide
You should make sure your code follows the given Java style guide.
Further Deductions
Additional deductions may be given for other issues or errors in your code. These deductions may now be unbounded, up to 5 marks. This include but not limited to
- run-time error.
- failure to follow instructions.
- improper designs (e.g., not following good OOP practice).
- not comenting
@SuppressWarnings
. - misuse of
@SuppressWarnings
(e.g., not necessary, not in smallest scope, etc).