Discussion 10: Reverse Indexing with Hashing
Solutions
Forward map (courses → students)
| Course | Enrolled Students (in order) |
|---|---|
| "CS 1110" | ["s1111", "s1234", "s9001"] |
| "CS 2110" | ["s1234", "s9001", "s1111", "s9001", "s2222"] |
| "CS 2800" | ["s7777", "s1111", null, "s1234"] |
| "CS 5154" | ["s9001", "s2222", "s7777"] |
Reverse map (students → courses)
| Student | Enrolled Courses (in order) |
|---|---|
| "s1111" | ["CS 1110", "CS 2110", "CS 2800"] |
| "s1234" | ["CS 1110", "CS 2110", "CS 2800"] |
| "s9001" | ["CS 1110", "CS 2110", "CS 5154"] |
| "s2222" | ["CS 2110", "CS 5154"] |
| "s7777" | ["CS 2800", "CS 5154"] |
buildReverseIndex() Constructor
Most of the work of the EnrollmentIndex class is done by its constructor, which initializes both its forward and reverse indices to satisfy their class invariants. Complete the definition of this constructor to build these indices in one pass over the rosters map. It may help to reference the Map interface documentation to remind yourself what operations are available.
|
|
|
|
Index Query Methods
Complete the definitions of the isEnrolled(), courseCountOf(), and enrollmentOf() methods, which run small queries against the indices that you built. Determine which index (forward or reverse) to use to get the best performance. Make sure that you carefully consider the case that a student or course may not be present in the index to avoid propagating any exceptions.
|
|
|
|
Runtime Analysis
Determine the runtimes of your implementations of the methods from part (b) in terms of \(|S|\), the total number of students, and \(|C|\), the total number of courses.
If you only had the forward index coursesToStudents, which of these operations would have a strictly worse runtime? Explain your answer.
isEnrolled() method, the calls to containsKey() and get() on the coursesToStudents (forward) map require expected \(O(1)\) time. The contains() call on the List returned by get() performs a linear search on this list, so has time linear in its length. In the worse case (where every single student is enrolled in one course), this has runtime \(O(|S|)\).In the courseCountOf() method, the calls to containsKey() and get() on the studentsToCourses (reverse) map require expected \(O(1)\) time. In addition, the size() call on the List returned by get() requires \(O(1)\) time, for an overall \(O(1)\) runtime.
In the enrollmentOf() method, the calls to containsKey() and get() on the coursesToStudents (forward) map require expected \(O(1)\) time. In addition, the size() call on the List returned by get() requires \(O(1)\) time, for an overall \(O(1)\) runtime.
If we only had the forward index, we would not be abe to efficiently implement the courseCountOf() method. To determine which courses student s was enrolled in, we’d need to iterate over all of the keys in the coursesToStudents map and check whether its roster contains s.
|
|
|
|
This loop runs for \(O(|C|)\) iterations. In each iteration, the contains() call requires an \(O(|S|)\) linear search of the student list, for an overall \(O(|C| \cdot |S|)\) runtime.
Iterator Methods
Complete the definitions of the students(), coursesOf(), and studentsIn() methods, which each return an Iterator. In the case of a student or course that is not present in the index, return Collections.emptyIterator(). You should not need to define any nested iterator classes to implement these methods.
|
|
|
|