-
Notifications
You must be signed in to change notification settings - Fork 545
Palindrome Product Exercise #501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
a4e9b1d
cbb099c
ca7fd00
ba4508f
d684347
e8f28f1
73fc7ff
32a0dcb
6ac02e1
e6decd8
2509fbb
66f082a
caccc36
e0357d5
6d6cd51
9ffc8f6
766f91d
aed2829
7144282
ce2a7ac
4ee8164
d536ad7
b21b310
2c0f325
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[package] | ||
name = "palindrome-products" | ||
version = "0.1.0" | ||
authors = ["Kirill Meng <[email protected]>"] | ||
|
||
[dependencies] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Palindrome Products | ||
Detect palindrome products in a given range. | ||
|
||
A palindromic number is a number that remains the same when its digits are | ||
reversed. For example, `121` is a palindromic number but `112` is not. | ||
|
||
Given a range of numbers, find the largest and smallest palindromes which | ||
are products of numbers within that range. | ||
|
||
Your solution should return the largest and smallest palindromes, along with the | ||
factors of each within the range. If the largest or smallest palindrome has more | ||
than one pair of factors within the range, then return all the pairs. | ||
|
||
## Example 1 | ||
|
||
Given the range `[1, 9]` (both inclusive)... | ||
|
||
And given the list of all possible products within this range: | ||
`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 21, 24, 27, 20, 28, 32, 36, 25, 30, 35, 40, 45, 42, 48, 54, 49, 56, 63, 64, 72, 81]` | ||
|
||
The palindrome products are all single digit numbers (in this case): | ||
`[1, 2, 3, 4, 5, 6, 7, 8, 9]` | ||
|
||
The smallest palindrome product is `1`. Its factors are `(1, 1)`. | ||
The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. | ||
|
||
## Example 2 | ||
|
||
Given the range `[10, 99]` (both inclusive)... | ||
|
||
The smallest palindrome product is `121`. Its factors are `(11, 11)`. | ||
The largest palindrome product is `9009`. Its factors are `(91, 99)`. | ||
|
||
## Rust Installation | ||
Refer to the [exercism help page][help-page] for Rust installation and learning | ||
resources. | ||
|
||
## Writing the Code | ||
|
||
Execute the tests with: | ||
|
||
```bash | ||
$ cargo test | ||
``` | ||
|
||
All but the first test have been ignored. After you get the first test to | ||
pass, remove the ignore flag (`#[ignore]`) from the next test and get the tests | ||
to pass again. The test file is located in the `tests` directory. You can | ||
also remove the ignore flag from all the tests to get them to run all at once | ||
if you wish. | ||
|
||
Make sure to read the [Modules](https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html) chapter if you | ||
haven't already, it will help you with organizing your files. | ||
|
||
## Feedback, Issues, Pull Requests | ||
|
||
The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help! | ||
|
||
If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md). | ||
|
||
[help-page]: http://exercism.io/languages/rust | ||
[modules]: https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html | ||
[cargo]: https://doc.rust-lang.org/book/second-edition/ch14-00-more-about-cargo.html | ||
|
||
## Submitting Incomplete Solutions | ||
It's possible to submit an incomplete solution so you can see how others have completed the exercise. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
#[derive(Debug, PartialEq)] | ||
pub enum Error{ | ||
Empty, | ||
RangeFailure, | ||
} | ||
|
||
#[allow(dead_code)] | ||
#[derive(Debug, PartialEq)] | ||
pub struct ReturnVals{ | ||
pub result: i32, | ||
pub factors: Vec<(i32, i32)>, | ||
} | ||
|
||
pub fn get_smallest_palindrome_product(min: i32, max: i32) -> Result<ReturnVals, Error>{ | ||
if min > max{ | ||
return Err(Error::RangeFailure); | ||
} | ||
|
||
let palindrome: Result<i32, Error> = get_smallest_palindrome(min, max); | ||
if palindrome.is_err(){ | ||
return Err(palindrome.unwrap_err()); | ||
} | ||
let p = palindrome.unwrap(); | ||
Ok(ReturnVals{ | ||
result: p, | ||
factors: get_factors(p, min, max), | ||
}) | ||
} | ||
|
||
pub fn get_largest_palindrome_product(min: i32, max: i32) -> Result<ReturnVals, Error>{ | ||
if min > max{ | ||
return Err(Error::RangeFailure); | ||
} | ||
|
||
let palindrome: Result<i32, Error> = get_largest_palindrome(min, max); | ||
if palindrome.is_err(){ | ||
return Err(palindrome.unwrap_err()); | ||
} | ||
let p = palindrome.unwrap(); | ||
Ok(ReturnVals{ | ||
result: p, | ||
factors: get_factors(p, min, max), | ||
}) | ||
} | ||
|
||
fn get_factors(n: i32, min: i32, max:i32)-> Vec<(i32, i32)>{ | ||
let mut factors = Vec::new(); | ||
|
||
for number in min .. max{ | ||
let div = n/number; | ||
if n % number == 0 && div <= max && div >= min && !factors.contains(&(div, number)){ | ||
factors.push((number, n/number)); | ||
} | ||
} | ||
factors | ||
} | ||
|
||
fn get_smallest_palindrome(min: i32, max:i32)-> Result<i32, Error>{ | ||
let l:Vec<i32> = (min*min .. max*max).collect(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This approach isn't very efficient, particularly as the difference between min and max grows. You'd do better with a nested for loop: for i in min..(max+1) {
for j in i..(max + 1) {
if is_palindrome(i*j) {
...
}
}
} |
||
let filtered: Vec<i32> = l.iter() | ||
.cloned() | ||
.filter(|n| is_palindrome(*n) && has_factors(*n, min, max)) | ||
.collect::<Vec<i32>>(); | ||
if filtered.is_empty(){ | ||
return Err(Error::Empty); | ||
} else{ | ||
Ok(*filtered.iter().min().unwrap()) | ||
} | ||
} | ||
|
||
fn get_largest_palindrome(min: i32, max:i32)-> Result<i32, Error>{ | ||
let l:Vec<i32> = (min*min .. max*max).collect(); | ||
let filtered: Vec<i32> = l.iter() | ||
.cloned() | ||
.filter(|n| is_palindrome(*n) && has_factors(*n, min, max)) | ||
.collect::<Vec<i32>>(); | ||
if filtered.is_empty(){ | ||
return Err(Error::Empty); | ||
} else{ | ||
Ok(*filtered.iter().max().unwrap()) | ||
} | ||
|
||
} | ||
|
||
fn has_factors(n: i32, min:i32, max:i32)->bool{ | ||
let fac = get_factors(n, min, max); | ||
!fac.is_empty() | ||
} | ||
|
||
fn is_palindrome(s: i32)->bool{ | ||
let s1 = s.to_string(); | ||
let s2 = s1.chars().rev().collect::<String>(); | ||
|
||
s1 == s2 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#[derive(Debug, PartialEq)] | ||
pub enum Error{ | ||
Empty, | ||
RangeFailure, | ||
} | ||
#[derive(Debug, PartialEq)] | ||
pub struct ReturnVals{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be named |
||
pub result: i32, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No negative number can be a palindrome, so let's use an unsigned type for the palindromes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use |
||
pub factors: Vec<(i32, i32)>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not obvious to me what value there is in returning the factors here. Best case, there exist certain implementations for which one can get the factorization more or less for free when generating the palindrome. Worst case, the student has to go back and factorize their solutions after generation, which is at best an orthogonal problem. I see that the canonical data has them. There's no real explanation of why. I therefore propose that for the Rust track, we diverge from the canonical data by dropping the factors from the implementation and the tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do this, we'd end up with a much nicer type for Palindrome: pub type Palindrome = u64; |
||
} | ||
|
||
pub fn get_smallest_palindrome_product(_min: i32, _max:i32)->Result<ReturnVals, Error>{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a huge fan of having pub struct Palindrome {
pub value: i32,
pub factors: Vec<(i32, i32)>,
}
pub type Palindromes = Vec<Palindrome>;
// in the case of an empty range or no results, we can just return an empty Vec, which simplifies the API
pub fn get_palindrome_products(min: i32, max: i32) -> Palindromes {
unimplemented!("Find all palindromic numbers which are products of numbers in the inclusive range ({}..{})", min, max)
}
pub fn min(palindromes: &Palindromes) -> Option<Palindrome> {
unimplemented!("Return the palindrome of minimal value from the supplied list: {:?}", palindromes)
}
pub fn max(palindromes: &Palindromes) -> Option<Palindrome> {
unimplemented!("Return the palindrome of maximal value from the supplied list: {:?}", palindromes)
} The tests could then be written in such a way that for any given test range, the computation is then performed only once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per #476, we're avoiding the use of underscore-prefixed variables in favor of descriptive text in the |
||
unimplemented!(); | ||
} | ||
pub fn get_largest_palindrome_product(_min: i32, _max: i32)->Result<ReturnVals, Error>{ | ||
unimplemented!(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
extern crate palindrome_products; | ||
use palindrome_products::*; | ||
|
||
enum GET{ | ||
Smallest, | ||
Largest, | ||
} | ||
|
||
fn test(e: GET, (min, max): (i32, i32))->Result<ReturnVals, Error>{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function appears not to do very much. One possibility would be to remove it entirely. Another would be to expand it into a macro which writes individually-ignorable tests from the input data. I find the second option much cooler, and am willing to help you work on the macro if you need help. See the perfect-numbers tests to see how that might look. |
||
match e{ | ||
GET::Smallest => return get_smallest_palindrome_product(min, max), | ||
GET::Largest => return get_largest_palindrome_product(min, max), | ||
} | ||
} | ||
#[test] | ||
fn smallest_palindrome_single_digits(){ | ||
assert_eq!(test(GET::Smallest, (1,9)), Ok(ReturnVals{result: 1, factors: vec!((1,1))})); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first test in the tests file should be the one not ignored. There are at least two ways to accomplish this:
I do not have a preference between these options. |
||
fn largest_palindrome_single_digits(){ | ||
assert_eq!(test(GET::Largest, (1,9)), Ok(ReturnVals{result: 9, factors: vec!((1,9), (3,3))})); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn smallest_palindrome_double_digits(){ | ||
assert_eq!(test(GET::Smallest, (10,99)), Ok(ReturnVals{result: 121, factors: vec!((11,11))})) | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn largest_palindrome_double_digits(){ | ||
assert_eq!(test(GET::Largest, (10,99)), Ok(ReturnVals{result: 9009, factors: vec!((91,99))})) | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn smallest_palindrome_triple_digits(){ | ||
assert_eq!(test(GET::Smallest, (100,999)), Ok(ReturnVals{result: 10201, factors: vec!((101,101))})) | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn largest_palindrome_triple_digits(){ | ||
assert_eq!(test(GET::Largest, (100,999)), Ok(ReturnVals{result: 906609, factors: vec!((913,993))})) | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn smallest_palindrome_four_digits(){ | ||
assert_eq!(test(GET::Smallest, (1000,9999)), Ok(ReturnVals{result: 1002001, factors: vec!((1001,1001))})) | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn largest_palindrome_four_digits(){ | ||
assert_eq!(test(GET::Largest, (1000,9999)), Ok(ReturnVals{result: 99000099, factors: vec!((9901,9999))})) | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn empty_result_for_smallest_palindrome(){ | ||
assert_eq!(test(GET::Smallest, (1002,1003)), Err(Error::Empty)); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn empty_result_for_largest_palindrome(){ | ||
assert_eq!(test(GET::Largest, (15,15)), Err(Error::Empty)); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn error_smallest_palindrome_when_min_bt_max(){ | ||
assert_eq!(test(GET::Smallest, (1000,1)), Err(Error::RangeFailure)); | ||
} | ||
|
||
#[test] | ||
#[ignore] | ||
fn error_largest_palindrome_when_min_bt_max(){ | ||
assert_eq!(test(GET::Largest, (2,1)), Err(Error::RangeFailure)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When there exists canonical data, we use this version field to indicate when the problem was last synchronized to the canonical data. As such, we'd expect this to be
"1.1.0"
, because that's the canonical data version.