Starting from:

$35

CS2030- Lab 6: Class Roster Solved

Class Roster

Topic Coverage

 Optional

Functional Interface and Lambda

Problem Description

Your task is to read in a roster of students, the modules they take, the assessments they have completed, and the grade for each assessment. Then, given a query consisting of a triplet: a student, a module, and an assessment, retrieve the corresponding grade.

For instance, if the input is:

Steve CS1010 Lab3 A 

Steve CS1231 Test A+ Bruce CS2030 Lab1 C 
and the query is Steve CS1231 Test, the program should print A+.

In our scenario, a roster has zero or more students; a student takes zero or more modules, a module has zero or more assessments, and each assessment has exactly one grade. Each of these entities can be uniquely identified by a string.

This lab is a continuation of the lab on Immutable Map.

You will need to refer to the Java documentation of Optional to familiarize yourself with the APIs available.

Level 1

Write a Student class that stores the modules he/she reads in an immutable map via the put method. A student can read zero or more modules, with each module having a unique module code as its key.
 
jshell> new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1") 

$.. ==> Optional[{Lab1: B}] 

 

jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))) 

$.. ==> Tony: {CS2040: {{Lab1: B}}} 

 

jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get(

$.. ==> Optional[CS2040: {{Lab1: B}}] 

 

jshell> Student natasha = new Student("Natasha"); 
"CS204
class KeyableMap<V extends Keyable> implements Keyable { 

    ... } 
jshell> new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1") 

$.. ==> Optional[{Lab1: B}] 

 

jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))) 

$.. ==> Tony: {CS2040: {{Lab1: B}}} 

 

jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get("CS204

$.. ==> Optional[CS2040: {{Lab1: B}}] 

 

jshell> Student natasha = new Student("Natasha"); natasha ==> Natasha: {} 

 

jshell> natasha = natasha.put(new Module("CS2040").put(new Assessment("Lab1", "B"))) natasha ==> Natasha: {CS2040: {{Lab1: B}}} 

 

jshell> natasha.put(new Module("CS2030").put(new Assessment("PE", "A+")).put(new Assessment("Lab2

$.. ==> Natasha: {CS2040: {{Lab1: B}}, CS2030: {{PE: A+}, {Lab2: C}}} 

 

jshell> Student tony = new Student("Tony"); 

tony ==> Tony: {} 

 

jshell> tony = tony.put(new Module("CS1231").put(new Assessment("Test", "A-"))) 

tony ==> Tony: {CS1231: {{Test: A-}}} 

 

jshell> tony.put(new Module("CS2100").put(new Assessment("Test", "B")).put(new Assessment("Lab1",

$.. ==> Tony: {CS1231: {{Test: A-}}, CS2100: {{Test: B}, {Lab1: F}}} 

 

jshell> new Module("CS1231").put(new Assessment("Test", "A-")) instanceof KeyableMap 

$.. ==> true 

 

jshell> new Student("Tony").put(new Module("CS1231")) instanceof KeyableMap 

$.. ==> true 

 

jshell> /exit |  Goodbye 
   natasha ==> Natasha: {} 

 

jshell> natasha = natasha.put(new Module("CS2040").put(new Assessment("Lab1", "B"))) natasha ==> Natasha: {CS2040: {{Lab1: B}}} 

 
jshell> natasha.put(new Module("CS2030").put(new Assessment("PE", "A+")).put(new Assessment("Lab2

$.. ==> Natasha: {CS2040: {{Lab1: B}}, CS2030: {{PE: A+}, {Lab2: C}}} 

 

jshell> Student tony = new Student("Tony"); 

tony ==> Tony: {} 

 

jshell> tony = tony.put(new Module("CS1231").put(new Assessment("Test", "A-"))) 

tony ==> Tony: {CS1231: {{Test: A-}}} 

 

jshell> tony.put(new Module("CS2100").put(new Assessment("Test", "B")).put(new Assessment("Lab1", $.. ==> Tony: {CS1231: {{Test: A-}}, CS2100: {{Test: B}, {Lab1: F}}} 
Level 2
You will notice that the implementations of the Student and Module classes are very similar. Hence, by applying the abstraction principle, write a generic class KeyableMap to reduce the duplication.

Hint: KeyableMap<V> is a generic class that wraps around a String key (i.e. implements Keyable) and a

Map<String,V>. KeyableMap models an entity that contains an immutable map, but is also itself contained in another container (e.g. a student contains a map of modules but could be contained in a roster). The parameter type V is the type of the value of items stored in the immutable map; V must be a subtype of Keyable.

The class KeyableMap provides two core methods:

get(String key) which returns the item with the given key;

put(V item) which adds the key-value pair (item.getKey(),item) into the immutable map. The put method returns a KeyableMap. How do we restrict the classes bound to type V to be able to invoke the getKey method? The trick is to define the type parameter of Keyable as follows:


jshell> new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1") 

$.. ==> Optional[{Lab1: B}] 

 

jshell> new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1").getGrade() 

|  Error: 

|  cannot find symbol 

|    symbol:   method getGrade() 

|  new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1").getGrade() 

|  ^------------------------------------------------------------------------^ 

 

jshell> new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1").map(x -> x.getGrade()) $.. ==> Optional[B] 
jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get("CS204

$.. ==> Optional[{Lab1: B}] 

 

jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get("CS204 $.. ==> Optional[B] 
  Level 3
Notice that method chains below result in compilation errors:

jshell> new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1").getGrade() 

|  Error: 

|  cannot find symbol 

|    symbol:   method getGrade() 

|  new Module("CS2040").put(new Assessment("Lab1", "B")).get("Lab1").getGrade() 

|  ^------------------------------------------------------------------------^ 

 
jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get("CS204

|  Error: 

|  method get in class java.util.Optional<T> cannot be applied to given types; 

|    required: no arguments 

|    found: java.lang.String 

|    reason: actual and formal argument lists differ in length 
|  new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get("CS2040" ).

|  ^---------------------------------------------------------------------------------------------

This is because the Optional class does not have a getGrade() or get(String) method defined (although it does define a get() method which, other than for debugging purposes, should typically be avoided). Rather than chaining in the usual way, we do it through a map or flatMap. Let's start with map.

As expected, invoking getGrade() on an Optional results in a compilation error. However, we can perform a similar chaining effect by passing in the functionality of getGrade (either in the form of a lambda or method reference) to Optional's map method. Notice the return value is actually wrapped in another Optional. When using map, you can think of the operation as "taking the value out of the Optional box, transforming it via the function passed to map, and wrap the transformed value back in another Optional".

Now this is where things start to get interesting! Look at the following:

jshell> new Student("Tony").put(new Module("CS2040").put(new Assessment("Lab1", "B"))).get("CS204 $.. ==> Optional[Optional[{Lab1: B}]] 
Observe that the return value is an Optional wrapped around another Optional that wraps around the desired value! Why is this so? The difference lies in the return type of Assessment::getGrade (read getGrade method of the Assessment class) and Module::get. The former returns a String, while the latter returns an Optional.

In x -> x.getGrade() (or Assessment::getGrade), the transformed value is simply the grade, and this is wrapped in an Optional. However, passing x -> x.get("Lab1") in the above code snippet results in a transformed value of Optional. And this transformed value is wrapped around another Optional via the map operation!

As such, we use the flatMap method instead. You may think of flatMap as flattening the Optionals into a single one.

Now you are ready to create a roster.

Define a Roster class that stores the students in an immutable map via the put method. A roster can have zero or more students, with each student having a unique id as its key. Once again, notice the similarities between Roster, Student and Module.

jshell> Student natasha = new Student("Natasha"); natasha ==> Natasha: {} 

 

jshell> natasha = natasha.put(new Module("CS2040").put(new Assessment("Lab1", "B"))) natasha ==> Natasha: {CS2040: {{Lab1: B}}} 

 

jshell> natasha = natasha.put(new Module("CS2030").put(new Assessment("PE", "A+")).put(new Assess

natasha ==> Natasha: {CS2040: {{Lab1: B}}, CS2030: {{PE: A+}, {Lab2: C}}} 

 

jshell> Student tony = new Student("Tony"); 

tony ==> Tony: {} 

 

jshell> tony = tony.put(new Module("CS1231").put(new Assessment("Test", "A-"))) 

tony ==> Tony: {CS1231: {{Test: A-}}} 

 

jshell> tony = tony.put(new Module("CS2100").put(new Assessment("Test", "B")).put(new Assessment(

tony ==> Tony: {CS1231: {{Test: A-}}, CS2100: {{Test: B}, {Lab1: F}}} 

 

jshell> Roster roster = new Roster("AY1920").put(natasha).put(tony) 

roster ==> AY1920: {Natasha: {CS2040: {{Lab1: B}}, CS2030: { ... : {{Test: B}, {Lab1: F}}}}

 

jshell> roster roster ==> AY1920: {Natasha: {CS2040: {{Lab1: B}}, CS2030: {{PE: A+}, {Lab2: C}}}, Tony: {CS1231:

 

jshell> roster.get("Tony").flatMap(x -> x.get("CS1231")).flatMap(x -> x.get("Test")).map(Assessme

$.. ==> Optional[A-] 

 

jshell> roster.get("Natasha").flatMap(x -> x.get("CS2040")).flatMap(x -> x.get("Lab1")).map(Asses

$.. ==> Optional[B] 

 

jshell> roster.get("Tony").flatMap(x -> x.get("CS1231")).flatMap(x -> x.get("Exam")).map(Assessme

$.. ==> Optional.empty 

 

jshell> roster.get("Steve").flatMap(x -> x.get("CS1010")).flatMap(x -> x.get("Lab1")).map(Assessm

$.. ==> Optional.empty 

 

jshell> roster.getGrade("Tony","CS1231","Test") 

$.. ==> "A-" 

 

jshell> roster.getGrade("Natasha","CS2040","Lab1") 

$.. ==> "B" 

 

jshell> roster.getGrade("Tony","CS1231","Exam"); 

$.. ==> "No such record: Tony CS1231 Exam" 

 

jshell> roster.getGrade("Steve","CS1010","Lab1"); 

$.. ==> "No such record: Steve CS1010 Lab1" 

 

jshell> new Roster("AY1920").put(new Student("Tony")) instanceof KeyableMap $.. ==> true 
jshell> Roster roster = new Roster("AY1920") roster ==> AY1920: {} 

 

jshell> roster = roster.add("Natasha", "CS2040", "Lab1" , "B") 

roster ==> AY1920: {Natasha: {CS2040: {{Lab1: B}}}} 

 

jshell> roster.add("Tony", "CS1231", "Test", "A-") 

$.. ==> AY1920: {Natasha: {CS2040: {{Lab1: B}}}, Tony: {CS1231: {{Test: A-}}}} 

 

jshell> roster.add("Natasha", "CS1231", "Test", "A-") 

$.. ==> AY1920: {Natasha: {CS2040: {{Lab1: B}}, CS1231: {{Test: A-}}}} 
  Define a method called getGrade in Roster to answer the query from the user. The method takes in three String parameters, corresponds to the student id, the module code, and the assessment title, and returns the corresponding grade.

In cases where there are no such student, or the student does not read the given module, or the module does not have the corresponding assessment, then output No such record followed by details of the query. Here, you might find Optional::orElse useful.

Level 4
Include the add method in the Roster class that takes in the student id, the module code, the assessment title and the grade, so as to update the roster as shown in the sample run below:

 

 
 

jshell> roster.add("Natasha", "CS2040", "Test", "A-") $.. ==> AY1920: {Natasha: {CS2040: {{Lab1: B}, {Test: A-}}}} 

 

jshell> roster.add("Natasha", "CS2040", "Lab1", "A-") 

$.. ==> AY1920: {Natasha: {CS2040: {{Lab1: A-}}}} 

 

jshell> roster.getGrade("Natasha", "CS2040", "Lab1") 

$.. ==> "B" 

 

jshell> roster.getGrade("Natasha", "CS2040", "Test") 

$.. ==> "No such record: Natasha CS2040 Test" 

 

jshell> roster.getGrade("Natasha", "CS1231", "Lab1") 

$.. ==> "No such record: Natasha CS1231 Lab1" 

 

jshell> roster.getGrade("Tony", "CS2040", "Lab1") 

$.. ==> "No such record: Tony CS2040 Lab1" 
 

More products