//! ```
//! use std::collections::HashMap;
//! use algorithms::stable_matching::*;
-//!
+//!
//! // Generate a random instance and solve it
//! let problem = generate_random_instance(3)?;
//! let solution = solve_stable_matching(problem);
-//!
+//!
//! // All men should be matched
//! assert_eq!(solution.free_men.len(), 0);
//! # Ok::<(), &'static str>(())
//! ```
-use std::collections::{HashMap, HashSet};
-use rand::seq::SliceRandom;
use crate::algorithms::stable_matching::types::{Gender, Person, Preferences};
-
+use rand::seq::SliceRandom;
+use std::collections::{HashMap, HashSet};
/// The main structure representing a stable matching problem instance.
///
/// ## Type Safety
/// All operations on this structure return `Result` types to handle
/// error conditions gracefully without panics.
-#[derive(Debug,Clone)]
+#[derive(Debug, Clone)]
pub struct StableMatchingProblem {
/// All male participants in the matching
pub men: Vec<Person>,
/// let mut prefs = HashMap::new();
/// prefs.insert(1, Preferences::new(1, vec!)?);[11]
/// prefs.insert(2, Preferences::new(2, vec!)?);[12]
- ///
+ ///
/// let problem = StableMatchingProblem::new(men, women, prefs)?;
/// assert_eq!(problem.free_men.len(), 1);
/// # Ok::<(), &'static str>(())
if men.len() != women.len() {
return Err("Must have equal numbers of men and women");
}
-
+
// Initialize mutable state
let mut free_men = HashSet::new();
let mut proposal_history = HashMap::new();
-
+
for man in &men {
free_men.insert(man.id);
proposal_history.insert(man.id, HashSet::new());
}
-
+
Ok(StableMatchingProblem {
men,
women,
/// * `Ok(&Preferences)` - Reference to the person's preferences
/// * `Err(&'static str)` - Error if no preferences found for this person
pub fn get_preferences(&self, person_id: u32) -> Result<&Preferences, &'static str> {
- self.preferences.get(&person_id)
+ self.preferences
+ .get(&person_id)
.ok_or("No preferences found for person")
}
-
+
/// Checks if a woman is currently unengaged.
///
/// # Arguments
if let Some(current_man) = self.engagements.get(&woman_id) {
self.free_men.insert(*current_man);
}
-
+
// Create new engagement
self.engagements.insert(woman_id, man_id);
self.free_men.remove(&man_id);
.get(&man_id)
.map_or(false, |set| set.len() == self.women.len())
}
-
+
/// Finds the next woman this man should propose to according to his preferences.
///
/// Returns the highest-ranked woman in his preference list to whom he
pub fn next_woman_to_propose(&self, man_id: u32) -> Result<Option<u32>, &'static str> {
let prefs = self.get_preferences(man_id)?;
let proposed_set = self.proposal_history.get(&man_id).unwrap();
-
- Ok(prefs.ordered_ids
+
+ Ok(prefs
+ .ordered_ids
.iter()
.find(|&&woman_id| !proposed_set.contains(&woman_id))
.copied())
}
impl<S: 'static, A: 'static> State<S, A> {
-
/// Creates a new state computation from a function.
///
/// This is the fundamental constructor for the State monad.
/// assert_eq!(new_count, 6);
/// ```
pub fn new<F>(f: F) -> Self
- where F: FnOnce(S) -> (A, S) + 'static {
+ where
+ F: FnOnce(S) -> (A, S) + 'static,
+ {
State { run: Box::new(f) }
}
(f(a).run)(s1)
})
}
-
+
/// Executes the state computation with an initial state.
///
/// This "runs" the state monad computation, providing the initial state
/// * `Some(woman_id)` - Next woman to propose to
/// * `None` - If he has proposed to all women
fn find_next_proposal_target(state: &StableMatchingProblem, man_id: u32) -> Option<u32> {
- state.preferences.get(&man_id)
- .and_then(|prefs| {
- let proposed_set = state.proposal_history.get(&man_id)?;
- prefs.ordered_ids
- .iter()
- .find(|&&woman_id| !proposed_set.contains(&woman_id))
- .copied()
- })
+ state.preferences.get(&man_id).and_then(|prefs| {
+ let proposed_set = state.proposal_history.get(&man_id)?;
+ prefs
+ .ordered_ids
+ .iter()
+ .find(|&&woman_id| !proposed_set.contains(&woman_id))
+ .copied()
+ })
}
/// Pure function to determine if a woman prefers a new man over her current partner.
/// This function implements the core stability check. A matching is stable
/// if no woman would prefer to switch from her current partner to any man
/// who would also prefer to switch to her.
-fn woman_prefers_new_man(state: &StableMatchingProblem, woman_id: u32, new_man_id: u32, current_man_id: u32) -> bool {
- state.preferences.get(&woman_id)
+fn woman_prefers_new_man(
+ state: &StableMatchingProblem,
+ woman_id: u32,
+ new_man_id: u32,
+ current_man_id: u32,
+) -> bool {
+ state
+ .preferences
+ .get(&woman_id)
.map(|woman_prefs| {
- let new_pos = woman_prefs.ordered_ids.iter().position(|&id| id == new_man_id);
- let current_pos = woman_prefs.ordered_ids.iter().position(|&id| id == current_man_id);
-
+ let new_pos = woman_prefs
+ .ordered_ids
+ .iter()
+ .position(|&id| id == new_man_id);
+ let current_pos = woman_prefs
+ .ordered_ids
+ .iter()
+ .position(|&id| id == current_man_id);
+
match (new_pos, current_pos) {
(Some(new_p), Some(current_p)) => new_p < current_p,
_ => false,
if let Some(man_id) = get_next_free_man(&state) {
if let Some(woman_id) = find_next_proposal_target(&state, man_id) {
// Record the proposal
- state.proposal_history.get_mut(&man_id).unwrap().insert(woman_id);
-
+ state
+ .proposal_history
+ .get_mut(&man_id)
+ .unwrap()
+ .insert(woman_id);
+
match state.engagements.get(&woman_id) {
None => {
// Woman is free, engage
/// # use algorithms::stable_matching::*;
/// let problem = generate_random_instance(4)?;
/// let solution = solve_stable_matching(problem);
-///
+///
/// // Verify all men are matched
/// assert_eq!(solution.free_men.len(), 0);
/// assert_eq!(solution.engagements.len(), 4);
/// Solves the stable matching problem using functional unfold composition.
///
/// This implementation uses `std::iter::successors` to express the algorithm
-/// as an unfold (anamorphism) - generating successive problem states from
+/// as an unfold (anamorphism) - generating successive problem states from
/// the initial configuration until convergence to a stable matching.
///
/// # Arguments
///
/// # Functional Programming Note
/// This demonstrates the unfold pattern - the categorical dual of fold.
-/// While fold consumes structure to produce values, unfold generates
+/// While fold consumes structure to produce values, unfold generates
/// structure from an initial seed value.
///
/// # Examples
/// # use algorithms::stable_matching::*;
/// let problem = generate_random_instance(4)?;
/// let solution = solve_stable_matching(problem);
-///
+///
/// assert_eq!(solution.free_men.len(), 0);
/// assert_eq!(solution.engagements.len(), 4);
/// # Ok::<(), &'static str>(())
if current_problem.free_men.is_empty() {
None // Algorithm has converged - no more states to generate
} else {
- // Generate next state using monadic state transformation
+ // Generate next state using monadic state transformation
let (_, next_state) = update_state().run_state(current_problem.clone());
Some(next_state)
}
/// # use algorithms::stable_matching::*;
/// // Create a problem with 5 men and 5 women
/// let problem = generate_random_instance(5)?;
-///
+///
/// assert_eq!(problem.men.len(), 5);
/// assert_eq!(problem.women.len(), 5);
/// assert_eq!(problem.preferences.len(), 10); // 5 + 5
/// - **Research**: Study algorithm behavior on random instances
pub fn generate_random_instance(count: usize) -> Result<StableMatchingProblem, &'static str> {
let mut rng = rand::thread_rng();
-
+
// Create people using functional combinators
let men: Vec<Person> = (1..=count as u32)
- .map(|id| Person { id, gender: Gender::Male })
+ .map(|id| Person {
+ id,
+ gender: Gender::Male,
+ })
.collect();
-
+
let women: Vec<Person> = (1..=count as u32)
- .map(|id| Person { id, gender: Gender::Female })
+ .map(|id| Person {
+ id,
+ gender: Gender::Female,
+ })
.collect();
-
+
let mut preferences = HashMap::new();
-
+
// Generate men's preferences functionally
for man in &men {
let mut women_ids: Vec<u32> = (1..=count as u32).collect();
women_ids.shuffle(&mut rng);
-
+
let prefs = Preferences::new(man.id, women_ids)?;
preferences.insert(man.id, prefs);
}
-
- // Generate women's preferences functionally
+
+ // Generate women's preferences functionally
for woman in &women {
let mut men_ids: Vec<u32> = (1..=count as u32).collect();
men_ids.shuffle(&mut rng);
-
+
let prefs = Preferences::new(woman.id, men_ids)?;
preferences.insert(woman.id, prefs);
}
-
+
StableMatchingProblem::new(men, women, preferences)
-}
-
+}
+
#[cfg(test)]
mod tests {
use super::*;
-
+
#[test]
fn test_preferences_creation() {
let prefs = Preferences::new(1, vec![2, 3, 4]).unwrap();
assert!(prefs.prefers(2, 3).unwrap());
assert!(!prefs.prefers(4, 2).unwrap());
}
-
+
#[test]
fn test_problem_creation() {
let men = vec![
- Person { id: 1, gender: Gender::Male },
- Person { id: 2, gender: Gender::Male },
+ Person {
+ id: 1,
+ gender: Gender::Male,
+ },
+ Person {
+ id: 2,
+ gender: Gender::Male,
+ },
];
let women = vec![
- Person { id: 1, gender: Gender::Female },
- Person { id: 2, gender: Gender::Female },
+ Person {
+ id: 1,
+ gender: Gender::Female,
+ },
+ Person {
+ id: 2,
+ gender: Gender::Female,
+ },
];
-
+
let mut preferences = HashMap::new();
preferences.insert(1, Preferences::new(1, vec![1, 2]).unwrap()); // Man 1 prefs
preferences.insert(2, Preferences::new(2, vec![2, 1]).unwrap()); // Man 2 prefs
-
+
let problem = StableMatchingProblem::new(men, women, preferences).unwrap();
assert_eq!(problem.free_men.len(), 2);
}
let instance = result.unwrap();
// Check men and women count
- assert_eq!(instance.men.len(), n, "Incorrect number of men generated [attached_file:1]");
- assert_eq!(instance.women.len(), n, "Incorrect number of women generated [attached_file:1]");
+ assert_eq!(
+ instance.men.len(),
+ n,
+ "Incorrect number of men generated [attached_file:1]"
+ );
+ assert_eq!(
+ instance.women.len(),
+ n,
+ "Incorrect number of women generated [attached_file:1]"
+ );
// Check that preferences exist for each man and woman
for man in &instance.men {
fn test_basic_functionality() -> Result<(), &'static str> {
let problem = generate_random_instance(3)?;
let solution = solve_stable_matching(problem);
-
+
// All men should be matched
assert_eq!(solution.free_men.len(), 0);
assert_eq!(solution.engagements.len(), 3);
-
+
Ok(())
}
}