/// Reconstructs the actual optimal solution (which jobs to select)
/// Returns a vector of job indices in the optimal solution
- pub fn find_optimal_jobs(&self) -> Vec<usize> {
+ pub fn find_optimal_indices(&self) -> Vec<usize> {
if self.n == 0 {
return Vec::new();
}
if include_value > self.dp[i - 1] {
// Job i was included
- solution.push(self.jobs[job_idx].id);
+ solution.push(job_idx);
// Move to the predecessor
match self.predecessors[job_idx] {
solution
}
+ /// Convenience: map indices (from `find_optimal_indices`) back to original job IDs.
+ pub fn ids_for(&self, indices: &[usize]) -> Vec<usize> {
+ indices.iter().map(|&i| self.jobs[i].id).collect()
+ }
+
+ /// Read-only access to the (finish-time-sorted) jobs.
+ pub fn jobs(&self) -> &[Job] {
+ &self.jobs
+ }
+
/// Solves the weighted interval scheduling problem
/// Returns (maximum_weight, selected_job_ids)
pub fn solve(&mut self) -> (i32, Vec<usize>) {
let max_weight = self.find_max_weight();
- let selected_jobs = self.find_optimal_jobs();
- (max_weight, selected_jobs)
+ let indices = self.find_optimal_indices();
+ (max_weight, indices)
}
- /// Pretty prints the solution
- pub fn print_solution(&self, max_weight: i32, selected_jobs: &[usize]) {
+ /// Pretty prints the solution.
+ ///
+ /// `indices` are positions in the sorted `jobs` vector (as returned by
+ /// [`Self::find_optimal_indices`] / [`Self::solve`]), not original job IDs.
+ pub fn print_solution(&self, max_weight: i32, indices: &[usize]) {
println!("\n=== Weighted Interval Scheduling Solution ===");
println!("Maximum total weight: {}", max_weight);
println!("Selected jobs:");
- for &job_id in selected_jobs {
- // Find the job with this ID
- if let Some(job) = self.jobs.iter().find(|j| j.id == job_id) {
- println!(
- " Job {}: [{}→{}, weight={}]",
- job.id, job.start, job.finish, job.weight
- );
- }
+ let selected_ids: std::collections::HashSet<usize> =
+ indices.iter().map(|&i| self.jobs[i].id).collect();
+
+ for &i in indices {
+ let job = &self.jobs[i];
+ println!(
+ " Job {} (pos {}): [{}→{}, weight={}]",
+ job.id, i, job.start, job.finish, job.weight
+ );
}
println!("\nAll jobs (sorted by finish time):");
- for (_i, job) in self.jobs.iter().enumerate() {
- let selected = selected_jobs.contains(&job.id);
- let marker = if selected { "✓" } else { " " };
+ for (i, job) in self.jobs.iter().enumerate() {
+ let marker = if selected_ids.contains(&job.id) { "✓" } else { " " };
println!(
- " {} Job {}: [{}→{}, weight={}]",
- marker, job.id, job.start, job.finish, job.weight
+ " {} [{}] Job {}: [{}→{}, weight={}]",
+ marker, i, job.id, job.start, job.finish, job.weight
);
}
}
+
}
}
// Pure function to calculate total weight
-fn calculate_total_weight(jobs: &[Job], solution: &[usize]) -> i32 {
- solution
- .iter()
- .map(|&idx| jobs[idx].weight)
- .map(|w| i32::try_from(w).expect("Weight overflow"))
- .sum()
+fn calculate_total_weight(jobs: &[Job], indices: &[usize]) -> i32 {
+ indices.iter().map(|&i| jobs[i].weight).sum()
}
// Example 1: Basic test case
-// #[test]
-// fn test_basic_case_example() {
-// let jobs = create_jobs_basic();
-// let mut scheduler = WeightedScheduler::new(jobs.clone());
-// let (max_weight, solution) = scheduler.solve();
-//
-// // Verify solution is valid
-// assert!(is_valid_solution(&jobs, &solution));
-//
-// // Verify calculated weight matches
-// assert_eq!(max_weight, calculate_total_weight(&jobs, &solution));
-//
-// // For this specific case, optimal weight should be 9
-// // (jobs with weights 5 and 4, indices 1 and 3)
-// assert_eq!(max_weight, 9);
-// }
+#[test]
+fn test_basic_case_example() {
+ let jobs = create_jobs_basic();
+ let mut scheduler = WeightedScheduler::new(jobs.clone());
+ let (max_weight, solution) = scheduler.solve();
+ // Verify solution is valid
+ assert!(is_valid_solution(scheduler.jobs(), &solution));
+ // Verify calculated weight matches
+ assert_eq!(max_weight, calculate_total_weight(scheduler.jobs(), &solution));
+
+ // For this specific case, optimal weight should be 9
+ // (jobs with weights 5 and 4, indices 1 and 3)
+ assert_eq!(max_weight, 9);
+}
// Example 2: Case where greedy by weight fails
-// #[test]
-// fn test_greedy_by_weight_fails() {
-// let jobs = create_jobs_greedy_fails();
-// let mut scheduler = WeightedScheduler::new(jobs.clone());
-// let (max_weight, solution) = scheduler.solve();
-//
-// // Verify solution validity
-// assert!(is_valid_solution(&jobs, &solution));
-// assert_eq!(max_weight, calculate_total_weight(&jobs, &solution));
-//
-// // The optimal solution should choose the four short jobs (weight 12)
-// // rather than the single long job (weight 10)
-// assert_eq!(max_weight, 12);
-// assert_eq!(solution.len(), 4);
-// }
+#[test]
+fn test_greedy_by_weight_fails() {
+ let jobs = create_jobs_greedy_fails();
+ let mut scheduler = WeightedScheduler::new(jobs.clone());
+ let (max_weight, solution) = scheduler.solve();
+
+ // Verify solution validity
+ assert!(is_valid_solution(scheduler.jobs(), &solution));
+ assert_eq!(max_weight, calculate_total_weight(scheduler.jobs(), &solution));
+
+ // The optimal solution should choose the four short jobs (weight 12)
+ // rather than the single long job (weight 10)
+ assert_eq!(max_weight, 12);
+ assert_eq!(solution.len(), 4);
+}
// Example 3: Larger test case
-// #[test]
-// fn test_larger_case_example() {
-// let jobs = create_jobs_large_case();
-// let mut scheduler = WeightedScheduler::new(jobs.clone());
-// let (max_weight, solution) = scheduler.solve();
-//
-// // Verify solution validity
-// assert!(is_valid_solution(&jobs, &solution));
-// assert_eq!(max_weight, calculate_total_weight(&jobs, &solution));
-//
-// // For this case, we just ensure we get a reasonable result
-// assert!(max_weight > 0);
-// assert!(!solution.is_empty());
-// }
+#[test]
+fn test_larger_case_example() {
+ let jobs = create_jobs_large_case();
+ let mut scheduler = WeightedScheduler::new(jobs.clone());
+ let (max_weight, solution) = scheduler.solve();
+
+ // Verify solution validity
+ assert!(is_valid_solution(scheduler.jobs(), &solution));
+ assert_eq!(max_weight, calculate_total_weight(scheduler.jobs(), &solution));
+
+ // For this case, we just ensure we get a reasonable result
+ assert!(max_weight > 0);
+ assert!(!solution.is_empty());
+}
// Property-based test: solution should always be valid
-// #[test]
-// fn test_solution_validity_property() {
-// let test_cases = vec![
-// create_jobs_basic(),
-// create_jobs_greedy_fails(),
-// create_jobs_large_case(),
-// ];
-//
-// for jobs in test_cases {
-// let mut scheduler = WeightedScheduler::new(jobs.clone());
-// let (max_weight, solution) = scheduler.solve();
-//
-// // Property: solution must be valid (no overlapping jobs)
-// assert!(is_valid_solution(&jobs, &solution));
-//
-// // Property: calculated weight must match returned weight
-// assert_eq!(max_weight, calculate_total_weight(&jobs, &solution));
-// }
-// }
+#[test]
+fn test_solution_validity_property() {
+ let test_cases = vec![
+ create_jobs_basic(),
+ create_jobs_greedy_fails(),
+ create_jobs_large_case(),
+ ];
+
+ for jobs in test_cases {
+ let mut scheduler = WeightedScheduler::new(jobs.clone());
+ let (max_weight, solution) = scheduler.solve();
+
+ // Property: solution must be valid (no overlapping jobs)
+ assert!(is_valid_solution(scheduler.jobs(), &solution));
+
+ // Property: calculated weight must match returned weight
+ assert_eq!(max_weight, calculate_total_weight(scheduler.jobs(), &solution));
+ }
+}
// Functional testing pattern: test with transformations
#[test]
assert_eq!(weight1, weight2);
}
-// Test edge case: all jobs overlap
#[test]
fn test_all_jobs_overlap() {
let jobs = vec![
let mut scheduler = WeightedScheduler::new(jobs.clone());
let (max_weight, solution) = scheduler.solve();
- // Should pick the highest weight job (12)
assert_eq!(max_weight, 12);
assert_eq!(solution.len(), 1);
- assert_eq!(solution[0], 2); // Job with weight 12
-}
-#[test]
-fn test_empty_jobs() {
- let mut scheduler = WeightedScheduler::new(vec![]);
- let (max_weight, solution) = scheduler.solve();
- assert_eq!(max_weight, 0);
- assert!(solution.is_empty());
+ // The selected index should point to the highest-weight job
+ let selected = &jobs[solution[0]];
+ assert_eq!(selected.weight, 12);
+ assert_eq!(selected.id, 2);
}
#[test]