What about introducing another level of indirection via a third HashMap
?
struct Enrollments {
next_id: usize,
courses: HashMap<String, Vec<usize>>,
students: HashMap<String, Vec<usize>>,
enrollments: HashMap<usize, Enrollment>,
}
struct Enrollment {
student: String,
course: String,
...
}
Then when you insert a new enrollment, you create an index and update the courses
and students
tables accordingly.
Here is the rest of my code:
impl Enrollment {
fn new(course: impl Into<String>, student: impl Into<String>) -> Self {
Enrollment {
course: course.into(),
student: student.into(),
}
}
}
impl Enrollments {
pub fn register(&mut self, enrollment: Enrollment) {
let id = self.next_id;
self.next_id += 1;
self.courses
.entry(enrollment.course.clone())
.or_default()
.push(id);
self.students
.entry(enrollment.student.clone())
.or_default()
.push(id);
self.enrollments.insert(id, enrollment);
}
pub fn by_course_name<'a>(&'a self, name: &str) -> impl Iterator<Item = &'a Enrollment> + 'a {
self.courses
.get(name)
.map(|ids| ids.as_slice())
.unwrap_or_default()
.iter()
.map(|enrollment_id| &self.enrollments[enrollment_id])
}
pub fn by_student_name<'a>(&'a self, name: &str) -> impl Iterator<Item = &'a Enrollment> + 'a {
self.students
.get(name)
.map(|ids| ids.as_slice())
.unwrap_or_default()
.iter()
.map(|enrollment_id| &self.enrollments[enrollment_id])
}
}
(playground)
Then when you have a main()
like this:
fn main() {
let mut enrollments = Enrollments::default();
enrollments.register(Enrollment::new("Maths", "Michael"));
enrollments.register(Enrollment::new("Maths", "Julia"));
enrollments.register(Enrollment::new("Programming", "Michael"));
println!("Math enrollments:");
for enrollment in enrollments.by_course_name("Maths") {
println!(" {:?}", enrollment);
}
println!("Michael's courses:");
for enrollment in enrollments.by_student_name("Michael") {
println!(" {:?}", enrollment);
}
}
it will generate the expected output
Math enrollments:
Enrollment { student: "Michael", course: "Maths" }
Enrollment { student: "Julia", course: "Maths" }
Michael's courses:
Enrollment { student: "Michael", course: "Maths" }
Enrollment { student: "Michael", course: "Programming" }
The benefit of this method is you can have get_xxx_mut()
methods which do the appropriate lookup and give you a &mut Enrollment
without worrying about smart pointers or mutexes.