diff --git a/exercises/practice/palindrome-products/.meta/example.rs b/exercises/practice/palindrome-products/.meta/example.rs index a39c87fda..00ca6d5aa 100644 --- a/exercises/practice/palindrome-products/.meta/example.rs +++ b/exercises/practice/palindrome-products/.meta/example.rs @@ -1,19 +1,30 @@ -/// `Palindrome` is a newtype which only exists when the contained value is a palindrome number in base ten. -/// -/// A struct with a single field which is used to constrain behavior like this is called a "newtype", and its use is -/// often referred to as the "newtype pattern". This is a fairly common pattern in Rust. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] -pub struct Palindrome(u64); +use std::cmp::Ordering; +use std::collections::HashSet; + +#[derive(Debug, Clone)] +pub struct Palindrome { + value: u64, + factors: HashSet<(u64, u64)>, +} impl Palindrome { - /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. - pub fn new(value: u64) -> Option { - is_palindrome(value).then_some(Palindrome(value)) + pub fn new(value: u64, first_factors: (u64, u64)) -> Palindrome { + Self { + value, + factors: HashSet::from([first_factors]), + } + } + + pub fn add_factor(&mut self, factor: (u64, u64)) -> bool { + self.factors.insert(factor) + } + + pub fn value(&self) -> u64 { + self.value } - /// Get the value of this palindrome. - pub fn into_inner(self) -> u64 { - self.0 + pub fn factors(&self) -> &HashSet<(u64, u64)> { + &self.factors } } @@ -22,14 +33,28 @@ pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome let mut pmax: Option = None; for i in min..=max { for j in i..=max { - if let Some(palindrome) = Palindrome::new(i * j) { - pmin = match pmin { - None => Some(palindrome), - Some(prev) => Some(prev.min(palindrome)), + let p = i * j; + if is_palindrome(p) { + pmin = match pmin.as_ref().map(|prev| prev.value.cmp(&p)) { + Some(Ordering::Less) => pmin, + Some(Ordering::Equal) => { + if i <= j { + pmin.as_mut().unwrap().add_factor((i, j)); + } + pmin + } + Some(Ordering::Greater) | None => Some(Palindrome::new(p, (i, j))), }; - pmax = match pmax { - None => Some(palindrome), - Some(prev) => Some(prev.max(palindrome)), + + pmax = match pmax.as_ref().map(|prev| prev.value.cmp(&p)) { + Some(Ordering::Greater) => pmax, + Some(Ordering::Equal) => { + if i <= j { + pmax.as_mut().unwrap().add_factor((i, j)); + } + pmax + } + Some(Ordering::Less) | None => Some(Palindrome::new(p, (i, j))), }; } } diff --git a/exercises/practice/palindrome-products/.meta/test_template.tera b/exercises/practice/palindrome-products/.meta/test_template.tera index e184921f5..eb1bf65aa 100644 --- a/exercises/practice/palindrome-products/.meta/test_template.tera +++ b/exercises/practice/palindrome-products/.meta/test_template.tera @@ -1,42 +1,26 @@ use palindrome_products::*; - -{# - These first two custom test cases are for the object-oriented design of the exercise. - They don't fit the structure of the upstream tests, so they're implemented here. -#} - -#[test] -#[ignore] -/// test `Palindrome::new` with valid input -fn palindrome_new_return_some() { - for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { - assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); - } -} - -#[test] -#[ignore] -/// test `Palindrome::new` with invalid input -fn palindrome_new_return_none() { - for v in [12, 2322, 23443, 1233211, 8932343] { - assert_eq!(Palindrome::new(v), None); - } -} +use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] fn {{ test.description | make_ident }}() { - {%- if test.property == "smallest" %} - let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(min, _)| min.into_inner()); - {%- else %} - let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(_, max)| max.into_inner()); - {%- endif%} + let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}); + {%- if test.expected.error is defined or not test.expected.value %} - let expected = None; + assert!(output.is_none()); {%- else %} - let expected = Some({{ test.expected.value }}); + assert!(output.is_some()); + + {% if test.property == "smallest" %} + let (pal, _) = output.unwrap(); + {%- else %} + let (_, pal) = output.unwrap(); + {%- endif%} + assert_eq!(pal.value(), {{ test.expected.value }}); + assert_eq!(pal.factors(), &HashSet::from([ + {{- test.expected.factors | join(sep=", ") | replace(from="[", to="(") | replace(from="]", to=")") -}} + ])); {%- endif%} - assert_eq!(output, expected); } {% endfor -%} diff --git a/exercises/practice/palindrome-products/src/lib.rs b/exercises/practice/palindrome-products/src/lib.rs index 15d3fb609..81b41ff1c 100644 --- a/exercises/practice/palindrome-products/src/lib.rs +++ b/exercises/practice/palindrome-products/src/lib.rs @@ -1,19 +1,15 @@ -/// `Palindrome` is a newtype which only exists when the contained value is a palindrome number in base ten. -/// -/// A struct with a single field which is used to constrain behavior like this is called a "newtype", and its use is -/// often referred to as the "newtype pattern". This is a fairly common pattern in Rust. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Palindrome(u64); +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Palindrome; impl Palindrome { - /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. - pub fn new(value: u64) -> Option { - todo!("if the value {value} is a palindrome return Some, otherwise return None"); + pub fn value(&self) -> u64 { + todo!("return the value of the palindrome") } - /// Get the value of this palindrome. - pub fn into_inner(self) -> u64 { - todo!("return inner value of a Palindrome"); + pub fn factors(&self) -> &HashSet<(u64, u64)> { + todo!("return the set of factors of the palindrome") } } diff --git a/exercises/practice/palindrome-products/tests/palindrome-products.rs b/exercises/practice/palindrome-products/tests/palindrome-products.rs index 711fb9339..d85eaabe3 100644 --- a/exercises/practice/palindrome-products/tests/palindrome-products.rs +++ b/exercises/practice/palindrome-products/tests/palindrome-products.rs @@ -1,122 +1,128 @@ use palindrome_products::*; +use std::collections::HashSet; #[test] -/// test `Palindrome::new` with valid input -fn palindrome_new_return_some() { - for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { - assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); - } -} - -#[test] -#[ignore] -/// test `Palindrome::new` with invalid input -fn palindrome_new_return_none() { - for v in [12, 2322, 23443, 1233211, 8932343] { - assert_eq!(Palindrome::new(v), None); - } -} - -#[test] -#[ignore] fn find_the_smallest_palindrome_from_single_digit_factors() { - let output = palindrome_products(1, 9).map(|(min, _)| min.into_inner()); - let expected = Some(1); - assert_eq!(output, expected); + let output = palindrome_products(1, 9); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 1); + assert_eq!(pal.factors(), &HashSet::from([(1, 1)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_single_digit_factors() { - let output = palindrome_products(1, 9).map(|(_, max)| max.into_inner()); - let expected = Some(9); - assert_eq!(output, expected); + let output = palindrome_products(1, 9); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 9); + assert_eq!(pal.factors(), &HashSet::from([(1, 9), (3, 3)])); } #[test] #[ignore] fn find_the_smallest_palindrome_from_double_digit_factors() { - let output = palindrome_products(10, 99).map(|(min, _)| min.into_inner()); - let expected = Some(121); - assert_eq!(output, expected); + let output = palindrome_products(10, 99); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 121); + assert_eq!(pal.factors(), &HashSet::from([(11, 11)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_double_digit_factors() { - let output = palindrome_products(10, 99).map(|(_, max)| max.into_inner()); - let expected = Some(9009); - assert_eq!(output, expected); + let output = palindrome_products(10, 99); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 9009); + assert_eq!(pal.factors(), &HashSet::from([(91, 99)])); } #[test] #[ignore] fn find_the_smallest_palindrome_from_triple_digit_factors() { - let output = palindrome_products(100, 999).map(|(min, _)| min.into_inner()); - let expected = Some(10201); - assert_eq!(output, expected); + let output = palindrome_products(100, 999); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 10201); + assert_eq!(pal.factors(), &HashSet::from([(101, 101)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_triple_digit_factors() { - let output = palindrome_products(100, 999).map(|(_, max)| max.into_inner()); - let expected = Some(906609); - assert_eq!(output, expected); + let output = palindrome_products(100, 999); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 906609); + assert_eq!(pal.factors(), &HashSet::from([(913, 993)])); } #[test] #[ignore] fn find_the_smallest_palindrome_from_four_digit_factors() { - let output = palindrome_products(1000, 9999).map(|(min, _)| min.into_inner()); - let expected = Some(1002001); - assert_eq!(output, expected); + let output = palindrome_products(1000, 9999); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 1002001); + assert_eq!(pal.factors(), &HashSet::from([(1001, 1001)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_four_digit_factors() { - let output = palindrome_products(1000, 9999).map(|(_, max)| max.into_inner()); - let expected = Some(99000099); - assert_eq!(output, expected); + let output = palindrome_products(1000, 9999); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 99000099); + assert_eq!(pal.factors(), &HashSet::from([(9901, 9999)])); } #[test] #[ignore] fn empty_result_for_smallest_if_no_palindrome_in_the_range() { - let output = palindrome_products(1002, 1003).map(|(min, _)| min.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(1002, 1003); + assert!(output.is_none()); } #[test] #[ignore] fn empty_result_for_largest_if_no_palindrome_in_the_range() { - let output = palindrome_products(15, 15).map(|(_, max)| max.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(15, 15); + assert!(output.is_none()); } #[test] #[ignore] fn error_result_for_smallest_if_min_is_more_than_max() { - let output = palindrome_products(10000, 1).map(|(min, _)| min.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(10000, 1); + assert!(output.is_none()); } #[test] #[ignore] fn error_result_for_largest_if_min_is_more_than_max() { - let output = palindrome_products(2, 1).map(|(_, max)| max.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(2, 1); + assert!(output.is_none()); } #[test] #[ignore] fn smallest_product_does_not_use_the_smallest_factor() { - let output = palindrome_products(3215, 4000).map(|(min, _)| min.into_inner()); - let expected = Some(10988901); - assert_eq!(output, expected); + let output = palindrome_products(3215, 4000); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 10988901); + assert_eq!(pal.factors(), &HashSet::from([(3297, 3333)])); }