Skip to content

Commit

Permalink
palindrome-products: require factors in solution
Browse files Browse the repository at this point in the history
  • Loading branch information
ellnix committed Jan 29, 2025
1 parent 0f32def commit a924024
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 119 deletions.
63 changes: 44 additions & 19 deletions exercises/practice/palindrome-products/.meta/example.rs
Original file line number Diff line number Diff line change
@@ -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<Palindrome> {
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
}
}

Expand All @@ -22,14 +33,28 @@ pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome
let mut pmax: Option<Palindrome> = 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))),
};
}
}
Expand Down
46 changes: 15 additions & 31 deletions exercises/practice/palindrome-products/.meta/test_template.tera
Original file line number Diff line number Diff line change
@@ -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 -%}
20 changes: 8 additions & 12 deletions exercises/practice/palindrome-products/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Palindrome> {
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")
}
}

Expand Down
120 changes: 63 additions & 57 deletions exercises/practice/palindrome-products/tests/palindrome-products.rs
Original file line number Diff line number Diff line change
@@ -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)]));
}

0 comments on commit a924024

Please sign in to comment.