From e0191efc832d5d1eeb2ae074849b77b2ef869c7b Mon Sep 17 00:00:00 2001 From: Chuck King Date: Sat, 17 Feb 2024 20:57:34 -0600 Subject: [PATCH] refactored to use people traits (#38) * refactored to use people traits * fixed test name --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/gift_circle.rs | 256 ++++++++------------------------------------- src/main.rs | 5 +- src/people.rs | 216 +++++++++++++++++++++++++++++++++++++- 5 files changed, 261 insertions(+), 220 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5818bac..cecd424 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,7 +157,7 @@ dependencies = [ [[package]] name = "gift_circle" -version = "0.10.0" +version = "0.11.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 28021a5..adaac18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gift_circle" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = ["Chuck King"] diff --git a/src/gift_circle.rs b/src/gift_circle.rs index 7c01638..daaa2fa 100644 --- a/src/gift_circle.rs +++ b/src/gift_circle.rs @@ -1,109 +1,44 @@ use std::u16; use anyhow::{anyhow, Result}; -use counter::Counter; use rand::seq::SliceRandom; -use super::group::Group; -use super::people::People; +use super::people::{People, PeopleCycle}; use super::person::Person; -fn largest_group(persons: &[Person]) -> Group { - let largest = persons - .iter() - .map(|p| p.group_number.unwrap()) - .collect::>() // gets the count per group number - .most_common_ordered()[0]; // grab the largest group by count - let g_number = largest.0; - let g_size = largest.1 as u16; - Group::new(g_number, g_size) +fn move_person(from_people: &mut People, to_people: &mut People, person: &Person) { + from_people.retain(|p| p != person); + to_people.push(person.clone()); } -fn largest_non_prev_group(persons: &[Person], previous_group: u16) -> Group { - let largest = persons - .iter() - .filter(|p| p.group_number.unwrap() != previous_group) - .map(|p| p.group_number) - .collect::>() - .most_common_ordered()[0]; - let g_number = largest.0.unwrap(); - let g_size = largest.1 as u16; - Group::new(g_number, g_size) -} - -fn has_possible_hamiltonian_path(persons: &[Person]) -> bool { - (largest_group(persons).size as usize * 2) <= persons.len() -} - -fn get_duplicated_names(persons: &[Person]) -> Vec { - let duplicated_names = persons - .iter() - .map(|p| p.name.clone()) - .collect::>() - .iter() - .filter(|(_, the_name_count)| **the_name_count > 1) - .map(|(the_name, _)| the_name.clone()) - .collect(); - duplicated_names -} - -fn first_and_last_groups_are_different(persons: &[Person]) -> bool { - // Last person gives gift to first person so can't be in the same group. - - let first_group = persons.first().unwrap().group_number.unwrap(); - let last_group = persons.last().unwrap().group_number.unwrap(); - - first_group != last_group -} - -fn has_no_consecutive_group_numbers(persons: &[Person]) -> bool { - let mut previous_group: u16 = 0; - for person in persons.iter() { - if person.group_number.unwrap() == previous_group { - return false; - } - previous_group = person.group_number.unwrap(); - } - true -} - -fn is_gift_circle_valid(persons: &[Person]) -> bool { - first_and_last_groups_are_different(persons) && has_no_consecutive_group_numbers(persons) -} - -fn move_person(from_persons: &mut People, to_persons: &mut People, person: &Person) { - from_persons.retain(|p| p != person); - to_persons.push(person.clone()); -} - -fn generate_path(from_persons: &[Person]) -> People { - // Go through the list of available participants and generate a gift path +fn generate_path(from_people: &People) -> People { + // Go through the list of available people and generate a gift path // where noone gives a gift to anyone in their same group. - // Preserve the from_persons vec for follow on attempts by working with a cloned vec - let mut available_persons: People = from_persons.to_owned(); + // Preserve the from_people vec for follow on attempts by working with a cloned vec + let mut available_people: People = from_people.to_owned(); // Build up the path by adding persons with different group numbers - let mut persons_path: People = vec![]; + let mut people_path: People = vec![]; let mut previous_group: u16 = 0; - while !available_persons.is_empty() { + while !available_people.is_empty() { // If the largest group is half (or more) than the total remaining, we have // to pick someone from that group. Otherwise, pick randomly. - let largest_np_group = largest_non_prev_group(&available_persons, previous_group); + let largest_np_group = available_people.largest_non_prev_group(previous_group); - let candidates: People = if (largest_np_group.size as usize * 2) > available_persons.len() { + let candidates: People = if (largest_np_group.size as usize * 2) > available_people.len() { // Build candidates list from the remaining persons in the largest group that is not the previous group - available_persons + available_people .iter() .filter(|&p| p.group_number.unwrap() == largest_np_group.number) .cloned() .collect::() } else { // Build the candidates list from all remaining persons not in the previous group - available_persons + available_people .iter() .filter(|&p| p.group_number.unwrap() != previous_group) .cloned() @@ -114,57 +49,57 @@ fn generate_path(from_persons: &[Person]) -> People { let choice = candidates.choose(&mut rand::thread_rng()).unwrap(); // Move the selected person from the available list to the path list - move_person(&mut available_persons, &mut persons_path, choice); + move_person(&mut available_people, &mut people_path, choice); previous_group = choice.group_number.unwrap(); } - persons_path + people_path } -fn generate_no_group_path(from_persons: &[Person]) -> People { - // Go through the list of available participants and generate a gift path. +fn generate_no_group_path(from_people: &People) -> People { + // Go through the list of available people and generate a gift path. - // Preserve the from_persons vec for follow on attempts by working with a cloned vec - let mut available_persons: People = from_persons.to_owned(); + // Preserve the from_people vec for follow on attempts by working with a cloned vec + let mut available_people: People = from_people.to_owned(); // Build up the path by adding random persons - let mut persons_path: People = vec![]; + let mut people_path: People = vec![]; - while !available_persons.is_empty() { + while !available_people.is_empty() { // Randomly select one person - let choice = available_persons + let choice = available_people .choose(&mut rand::thread_rng()) .unwrap() .clone(); // Move the selected person from the available list to the path list - move_person(&mut available_persons, &mut persons_path, &choice); + move_person(&mut available_people, &mut people_path, &choice); } - persons_path + people_path } -pub fn get_gift_circle(from_persons: People, use_groups: bool) -> Result { - if from_persons.len() <= 2 { +pub fn get_gift_circle(from_people: People, use_groups: bool) -> Result { + if from_people.len() <= 2 { return Err(anyhow!( "You must submit at least three people in order to form a gift circle." )); } - let duplicates: Vec = get_duplicated_names(&from_persons); + let duplicates: Vec = from_people.get_duplicated_names(); if !duplicates.is_empty() { return Err(anyhow!("Found duplicate names: {:#?}", duplicates)); } if use_groups { - if from_persons.iter().any(|p| p.group_number.is_none()) { + if from_people.has_empty_group() { return Err(anyhow!( "When using groups each participant must have a group assinged!" )); } - let possible_path = has_possible_hamiltonian_path(&from_persons); + let possible_path = from_people.has_possible_hamiltonian_path(); if !possible_path { return Err(anyhow!( "Sorry, no possible hamiltonian path with this set of groups." @@ -180,13 +115,13 @@ pub fn get_gift_circle(from_persons: People, use_groups: bool) -> Result while !have_valid_circle && attempt_count < MAX_ATTEMPTS { if use_groups { - gift_circle = generate_path(&from_persons); - if is_gift_circle_valid(&gift_circle) { + gift_circle = generate_path(&from_people); + if gift_circle.is_gift_circle_valid() { have_valid_circle = true; } } else { - gift_circle = generate_no_group_path(&from_persons); - if from_persons.len() == gift_circle.len() { + gift_circle = generate_no_group_path(&from_people); + if from_people.len() == gift_circle.len() { have_valid_circle = true; } } @@ -221,113 +156,8 @@ pub fn get_gift_circle(from_persons: People, use_groups: bool) -> Result #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_largest_group() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 1), - Person::new("Son", 2), - ]; - assert_eq!(largest_group(&participants), Group::new(1, 2u16)); - } - - #[test] - fn test_largest_non_prev_group() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 1), - Person::new("Son", 2), - Person::new("Daughter", 2), - ]; - assert_eq!( - largest_non_prev_group(&participants, 2), - Group::new(1, 2u16) - ); - } - - #[test] - fn test_has_possible_hamiltonian_path_true() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 1), - Person::new("Son", 2), - Person::new("Daughter", 3), - ]; - assert!(has_possible_hamiltonian_path(&participants)); - } - - #[test] - fn test_has_possible_hamiltonian_path_false() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 1), - Person::new("Son", 1), - Person::new("Daughter", 2), - ]; - assert!(!has_possible_hamiltonian_path(&participants)); - } - #[test] - fn test_get_duplicate_names() { - let participants = vec![Person::new("Mother", 1), Person::new("Mother", 1)]; - assert_eq!(get_duplicated_names(&participants).len(), 1); - } - - #[test] - fn test_first_and_last_groups_are_different_true() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 2), - Person::new("Son", 1), - Person::new("Daughter", 3), - ]; - assert!(first_and_last_groups_are_different(&participants)); - } - - #[test] - fn test_first_and_last_groups_are_different_false() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 2), - Person::new("Son", 1), - ]; - assert!(!first_and_last_groups_are_different(&participants)); - } - - #[test] - fn test_has_no_consecutive_group_numbers_true() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 2), - Person::new("Son", 1), - Person::new("Daughter", 3), - ]; - assert!(has_no_consecutive_group_numbers(&participants)); - } - - #[test] - fn test_has_no_consecutive_group_numbers_false() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 2), - Person::new("Son", 2), - Person::new("Daughter", 3), - ]; - assert!(!has_no_consecutive_group_numbers(&participants)); - } - - #[test] - fn test_is_gift_circle_valid_true() { - let participants = vec![ - Person::new("Father", 1), - Person::new("Mother", 2), - Person::new("Son", 1), - Person::new("Daughter", 3), - ]; - assert!(is_gift_circle_valid(&participants)); - } + use super::*; #[test] fn test_move_person() { @@ -342,26 +172,26 @@ mod tests { #[test] fn test_get_gift_circle_using_groups() { - let participants = vec![ + let people = vec![ Person::new("Father", 1), Person::new("Mother", 1), Person::new("Son", 2), Person::new("Daughter", 2), ]; - if let Ok(gift_circle) = get_gift_circle(participants, true) { + if let Ok(gift_circle) = get_gift_circle(people, true) { assert_eq!(gift_circle.len(), 4); } } #[test] fn test_get_gift_circle_not_using_groups() { - let participants = vec![ + let people = vec![ Person::new_no_group("Father"), Person::new_no_group("Mother"), Person::new_no_group("Son"), Person::new_no_group("Daughter"), ]; - if let Ok(gift_circle) = get_gift_circle(participants, false) { + if let Ok(gift_circle) = get_gift_circle(people, false) { assert_eq!(gift_circle.len(), 4); } } @@ -369,23 +199,23 @@ mod tests { #[test] #[should_panic] fn test_get_gift_circle_errors_with_too_few_entries() { - let participants = vec![ + let people = vec![ Person::new("Father", 1), Person::new("Mother", 1), Person::new("Son", 2), ]; - get_gift_circle(participants, true).unwrap(); + get_gift_circle(people, true).unwrap(); } #[test] #[should_panic] fn test_get_gift_circle_errors_with_duplicate_names() { - let participants = vec![ + let people = vec![ Person::new("Father", 1), Person::new("Mother", 1), Person::new("Son", 2), Person::new("Father", 3), ]; - get_gift_circle(participants, true).unwrap(); + get_gift_circle(people, true).unwrap(); } } diff --git a/src/main.rs b/src/main.rs index a7b611e..f1d838c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,10 +27,7 @@ fn run() -> Result<()> { people.push(person); } - #[allow(unused_assignments)] - let mut gift_circle: People = vec![]; - - gift_circle = get_gift_circle(people, args.use_groups)?; + let gift_circle: People = get_gift_circle(people, args.use_groups)?; let mut wtr = csv::Writer::from_writer(io::stdout()); diff --git a/src/people.rs b/src/people.rs index c39e537..06ac8b6 100644 --- a/src/people.rs +++ b/src/people.rs @@ -1,4 +1,218 @@ -use crate::Person; +use counter::Counter; + +use super::group::Group; +use super::person::Person; #[allow(dead_code)] pub type People = Vec; + +pub trait PeopleCycle { + fn get_duplicated_names(&self) -> Vec; + fn first_and_last_groups_are_different(&self) -> bool; + fn has_empty_group(&self) -> bool; + fn has_no_consecutive_group_numbers(&self) -> bool; + fn has_possible_hamiltonian_path(&self) -> bool; + fn is_gift_circle_valid(&self) -> bool; + fn largest_group(&self) -> Group; + fn largest_non_prev_group(&self, previous_group: u16) -> Group; +} + +impl PeopleCycle for People { + fn largest_group(&self) -> Group { + let largest = self + .iter() + .map(|p| p.group_number.unwrap()) + .collect::>() // gets the count per group number + .most_common_ordered()[0]; // grab the largest group by count + let g_number = largest.0; + let g_size = largest.1 as u16; + Group::new(g_number, g_size) + } + + fn largest_non_prev_group(&self, previous_group: u16) -> Group { + let largest = self + .iter() + .filter(|p| p.group_number.unwrap() != previous_group) + .map(|p| p.group_number) + .collect::>() + .most_common_ordered()[0]; + let g_number = largest.0.unwrap(); + let g_size = largest.1 as u16; + Group::new(g_number, g_size) + } + + fn has_possible_hamiltonian_path(&self) -> bool { + (self.largest_group().size as usize * 2) <= self.len() + } + + fn has_empty_group(&self) -> bool { + self.iter().any(|p| p.group_number.is_none()) + } + + fn get_duplicated_names(&self) -> Vec { + let duplicated_names = self + .iter() + .map(|p| p.name.clone()) + .collect::>() + .iter() + .filter(|(_, the_name_count)| **the_name_count > 1) + .map(|(the_name, _)| the_name.clone()) + .collect(); + duplicated_names + } + + fn first_and_last_groups_are_different(&self) -> bool { + // Last person gives gift to first person so can't be in the same group. + + let first_group = self.first().unwrap().group_number.unwrap(); + let last_group = self.last().unwrap().group_number.unwrap(); + + first_group != last_group + } + + fn has_no_consecutive_group_numbers(&self) -> bool { + let mut previous_group: u16 = 0; + for person in self.iter() { + if person.group_number.unwrap() == previous_group { + return false; + } + previous_group = person.group_number.unwrap(); + } + true + } + + fn is_gift_circle_valid(&self) -> bool { + self.first_and_last_groups_are_different() && self.has_no_consecutive_group_numbers() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_largest_group() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 1), + Person::new("Son", 2), + ]; + assert_eq!(people.largest_group(), Group::new(1, 2u16)); + } + + #[test] + fn test_largest_non_prev_group() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 1), + Person::new("Son", 2), + Person::new("Daughter", 2), + ]; + assert_eq!(people.largest_non_prev_group(2), Group::new(1, 2u16)); + } + + #[test] + fn test_has_possible_hamiltonian_path_true() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 1), + Person::new("Son", 2), + Person::new("Daughter", 3), + ]; + assert!(people.has_possible_hamiltonian_path()); + } + + #[test] + fn test_has_possible_hamiltonian_path_false() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 1), + Person::new("Son", 1), + Person::new("Daughter", 2), + ]; + assert!(!people.has_possible_hamiltonian_path()); + } + + #[test] + fn test_has_empty_group_false() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 1), + Person::new("Son", 1), + Person::new("Daughter", 2), + ]; + assert!(!people.has_empty_group()); + } + + #[test] + fn test_has_empty_group_true() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 1), + Person::new("Son", 1), + Person::new_no_group("Daughter"), + ]; + assert!(people.has_empty_group()); + } + + #[test] + fn test_get_duplicate_names() { + let people = vec![Person::new("Mother", 1), Person::new("Mother", 1)]; + assert_eq!(people.get_duplicated_names().len(), 1); + } + + #[test] + fn test_first_and_last_groups_are_different_true() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 2), + Person::new("Son", 1), + Person::new("Daughter", 3), + ]; + assert!(people.first_and_last_groups_are_different()); + } + + #[test] + fn test_first_and_last_groups_are_different_false() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 2), + Person::new("Son", 1), + ]; + assert!(!people.first_and_last_groups_are_different()); + } + + #[test] + fn test_has_no_consecutive_group_numbers_true() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 2), + Person::new("Son", 1), + Person::new("Daughter", 3), + ]; + assert!(people.has_no_consecutive_group_numbers()); + } + + #[test] + fn test_has_no_consecutive_group_numbers_false() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 2), + Person::new("Son", 2), + Person::new("Daughter", 3), + ]; + assert!(!people.has_no_consecutive_group_numbers()); + } + + #[test] + fn test_is_gift_circle_valid() { + let people = vec![ + Person::new("Father", 1), + Person::new("Mother", 2), + Person::new("Son", 1), + Person::new("Daughter", 3), + ]; + assert!(people.is_gift_circle_valid()); + } +}