From: jeff Date: Mon, 5 Oct 2026 10:25:58 +0000 (+0900) Subject: fixed some tests X-Git-Url: http://gitweb.hemminger.haus/?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=algorithms.git fixed some tests --- diff --git a/src/algorithms/heap/leftist_priority_heap.rs b/src/algorithms/heap/leftist_priority_heap.rs index 478a616..2cbb1d7 100644 --- a/src/algorithms/heap/leftist_priority_heap.rs +++ b/src/algorithms/heap/leftist_priority_heap.rs @@ -1,5 +1,5 @@ use std::fmt::Debug; -use std::rc::Rc; +use std::sync::Arc; /// A semigroup: a type with an associative combine operation pub trait Semigroup { @@ -25,8 +25,8 @@ pub enum Heap { Node { rank: usize, elem: T, - left: Rc>, - right: Rc>, + left: Arc>, + right: Arc>, }, } @@ -70,15 +70,15 @@ impl Heap { Heap::Node { rank, elem, - left: Rc::new(left), - right: Rc::new(right), + left: Arc::new(left), + right: Arc::new(right), } } - /// Try to unwrap an Rc: if we have the only reference, extract the heap for free. + /// Try to unwrap an Arc: if we have the only reference, extract the heap for free. /// If the heap is shared, clone it (necessary for persistence). - fn unwrap_or_clone(rc: Rc) -> Self { - Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) + fn unwrap_or_clone(rc: Arc) -> Self { + Arc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } /// Merge two heaps into one, maintaining heap and leftist properties. @@ -147,8 +147,8 @@ impl Heap { let singleton = Heap::Node { rank: 1, elem, - left: Rc::new(Heap::Empty), - right: Rc::new(Heap::Empty), + left: Arc::new(Heap::Empty), + right: Arc::new(Heap::Empty), }; self.merge(singleton) } diff --git a/src/algorithms/interview_scheduling/weighted_interval_scheduling.rs b/src/algorithms/interview_scheduling/weighted_interval_scheduling.rs index 714b60b..2b6bd0f 100644 --- a/src/algorithms/interview_scheduling/weighted_interval_scheduling.rs +++ b/src/algorithms/interview_scheduling/weighted_interval_scheduling.rs @@ -122,7 +122,7 @@ impl WeightedScheduler { /// 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 { + pub fn find_optimal_indices(&self) -> Vec { if self.n == 0 { return Vec::new(); } @@ -140,7 +140,7 @@ impl WeightedScheduler { 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] { @@ -158,39 +158,53 @@ impl WeightedScheduler { solution } + /// Convenience: map indices (from `find_optimal_indices`) back to original job IDs. + pub fn ids_for(&self, indices: &[usize]) -> Vec { + 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) { 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 = + 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 ); } } + } diff --git a/src/algorithms/stable_matching/bipartite.rs b/src/algorithms/stable_matching/bipartite.rs index 05bfd92..ce6bc9d 100644 --- a/src/algorithms/stable_matching/bipartite.rs +++ b/src/algorithms/stable_matching/bipartite.rs @@ -141,10 +141,6 @@ pub fn stable_match( // Proposer is now engaged free_left.remove(&proposer_id); - } else { - // Proposal rejected; proposer remains free for next iteration - free_left.remove(&proposer_id); - free_left.insert(proposer_id.clone()); } } else { // Proposer exhausted their preference list diff --git a/src/algorithms/stable_matching/gale_shapley.rs b/src/algorithms/stable_matching/gale_shapley.rs index 2964447..8fb9383 100644 --- a/src/algorithms/stable_matching/gale_shapley.rs +++ b/src/algorithms/stable_matching/gale_shapley.rs @@ -1,5 +1,5 @@ -use crate::StableMatchingGraph; use std::collections::HashMap; +use crate::bipartite::StableMatchingGraph; #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct AgentId(pub String); diff --git a/tests/test_weighted_interval_scheduling.rs b/tests/test_weighted_interval_scheduling.rs index 50c13a9..4a0198f 100644 --- a/tests/test_weighted_interval_scheduling.rs +++ b/tests/test_weighted_interval_scheduling.rs @@ -43,85 +43,79 @@ fn is_valid_solution(jobs: &[Job], solution: &[usize]) -> bool { } // 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] @@ -138,7 +132,6 @@ fn test_job_order_independence() { assert_eq!(weight1, weight2); } -// Test edge case: all jobs overlap #[test] fn test_all_jobs_overlap() { let jobs = vec![ @@ -151,18 +144,13 @@ fn test_all_jobs_overlap() { 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]