Skip to content

Implement exercise saddle-points #400

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

Merged
merged 24 commits into from
Dec 14, 2017
Merged

Conversation

hekrause
Copy link
Contributor

No description provided.

@hekrause
Copy link
Contributor Author

I don't know why the ensure-readmes-are-updated.sh script has a problem with the README.md.
I generated it with the command ./configlet generate rust --only saddle-points and then pushed it.

Copy link
Member

@petertseng petertseng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why the ensure-readmes-are-updated.sh script has a problem with the README.md.

I will point it out with a line comment.

It has a saddle point at (1, 0).

It's called a "saddle point" because it is greater than or equal to
every element in its row and the less than or equal to every element in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the less than

It has a saddle point at (1, 0).

It's called a "saddle point" because it is greater than or equal to
every element in its row and the less than or equal to every element in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that means you did not take exercism/problem-specifications@2f6a515 into account when generating the README. Please regenerate taking it into account.

Copy link
Member

@coriolinus coriolinus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing this exercise, @hekrause! There are a few changes I'd like to see, as described below.

Another change I'd like to see, but which isn't appropriate for a line comment: there are currently no tests which verify that the implementation can detect more than one solution, when more than one exists. Please add at least the case in the canonical data to ensure that implementations are complete.

@@ -0,0 +1,5 @@
[package]
name = "saddle-points"
version = "1.1.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version should match the version in canonical-data.json. That means that as of the current master branch of problem-specifications, it should be 1.0.0.

@@ -0,0 +1,4 @@

pub fn find_saddle_points(input: Vec<Vec<u64>>) -> Vec<(u64, u64)>{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to transfer ownership of the input for find_saddle_points to work. We want to teach people how to write idiomatic Rust, so in this case, this function should take &[&[u64]] (reference to slice of reference to slice of u64). Vec<Vec<u64>> should coerce transparently to the slice form.

@@ -0,0 +1,4 @@

pub fn find_saddle_points(input: Vec<Vec<u64>>) -> Vec<(u64, u64)>{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming the variable input causes a compiler warning due to the unused variable. While this doesn't break CI, I'd prefer to skip that by renaming the variable to _input or simply _.

use saddle_points::*;

#[test]
fn identify_single_saddle_point() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All test names should begin with test_.


#[test]
#[ignore]
fn identify_missing_saddle_point() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use of "missing" in this test name implies that one should be present. Please update the name of this function to make it clear that there are expected to be none.

I believe this is based on the canonical test "Can identify lack of saddle points when there are none". If so, consider naming this test based on that name. The test names don't need to precisely map to the canonical test descriptions, but it's best when there is at least some correspondence.

#[ignore]
fn identify_missing_saddle_point() {
let input: Vec<Vec<u64>> = vec![vec![1, 2, 3],
vec![3, 2, 1],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this test is based on the canonical test "Can identify lack of saddle points when there are none", this row of input data diverges from the equivalent row in the canonical data. Is there a reason for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it was a typo. :-)


#[test]
#[ignore]
fn non_quadratic_matrix_high() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-imperative quibble: change *quadratic* test names to use the term square instead. While "quadratic" isn't wrong, it has associations with the operation of squaring more than the shape of a matrix. Also, it's usually better to name things for clarity, and "square" is a much more common word.

If you feel strongly that the term should remain, it's fine; I don't feel that this is an essential change, just one I'd prefer.

pub fn find_saddle_points(input: Vec<Vec<u64>>) -> Vec<(u64, u64)>{
let mut saddle_points: Vec<(u64, u64)> = Vec::new();

let row_index = input.len();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is indeed an index, it is also not the index of any existing element in the input (it is the index of the first element beyond it), so to avoid confusion I would call this height instead.

let mut saddle_points: Vec<(u64, u64)> = Vec::new();

let row_index = input.len();
let column_index = input[0].len();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is indeed an index, it is also not the index of any existing element in the input (it is the index of the first element beyond it), so to avoid confusion I would call this height instead.

Same, except now width.


for i in 0..row_index {
for j in 0..column_index {
let row = &input[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is only based on i and not j, it would be possible to assign it in the outer loop and not the inner one. Unless that makes the code less understandable.

let row = &input[i];
let column = input.iter().map(|x| x[j]).collect::<Vec<u64>>();

let max = row.iter().max().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is only based on row, which is only based on i and not j, it would be possible to assign it in the outer loop and not the inner one. Unless that makes the code less understandable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the matrix is empty the two for loops prevent an unwrap on row in line 13.

}
}
}
return saddle_points;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest removing the return and the semicolon.

However, I cannot find a justification for this suggestion in https://aturon.github.io nor https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md which means I have no grounds backing up this suggestion. Nevertheless, it is suggested.

@coriolinus
Copy link
Member

Thanks @hekrause for updating these. As mentioned before (#400 (review)), there still isn't a test which validates that solutions can return multiple saddle points if they exist; there's a case for that already in the canonical data. Please add at least that case.

vec![5, 3, 2],
vec![6, 6, 7]];
fn test_identify_single_saddle_point() {
let input: &[&[u64]] = &[&[9, 8, 7],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting; I hadn't intended that you change the tests when I asked you to change the signature; a reference to Vec<Vec<u64>> should just work for a function which requires &[&[u64]].

I'm not sure whether to ask you to change this back. As of right now, I'm undecided: on the one hand, it's interesting to see that slice reference literals work at all. On the other hand, idiomatic code would be to specify the inputs as Vec<Vec<u64>> and then pass the reference and let the Deref trait do the work for you.

I'd like to solicit opinions from the other maintainers about which of these forms are better style.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is idiomatic, I would keep the vec then. Will be even more convinced if finding a source that tells us this, but I cannot.

We do have quite a few instances of using slices in tests in other exercises, but potentially changing them is a story for another day

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you mean in the tests should be vectors as in the first version but I should call the function with the &[&[u64]] parameters?And the slices should be from the vectors?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: leave the function signature as it is, which expects slices of slices, and change the tests so that they allocate Vecs and pass a reference to the function.


let column = input.iter().map(|x| x[j]).collect::<Vec<u64>>();
let row = &input[i];
let max = row.iter().max().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is only based on row, which is only based on i and not j, it would be possible to assign it in the outer loop and not the inner one. Unless that makes the code less understandable.

If the matrix is empty the two for loops prevent an unwrap on row in line 13.

Indeed, if a row is empty. In that case, I will now only optionally ask for row to be moved to the outer loop (it still can be), and agree that I was wrong to ask for this max line to be moved; it cannot be.

Copy link
Contributor Author

@hekrause hekrause Nov 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right but I would like to leave it like this because I think in the way it is now it is more readable.

@@ -0,0 +1,4 @@

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this empty line could be removed, and I think it should be. The fact that there aren't any use lines shouldn't affect things I don't think, because if they were present, we wouldn't start the file with an empty line either.

same

@@ -0,0 +1,24 @@

Copy link
Member

@petertseng petertseng Nov 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this empty line could be removed, and I think it should be. The fact that there aren't any use lines shouldn't affect things I don't think, because if they were present, we wouldn't start the file with an empty line either.

The empty line I intended to be removed has been removed. GitHub is now placing this comment on an unrelated line, so let us ignore it.

let vector: Vec<Vec<u64>> = vec![vec![9, 8, 7],
vec![5, 3, 2],
vec![6, 6, 7]];
let input: &[&[u64]] = &[&vector[0][..],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way of forming the input slice of slices is ugly, and is my fault. I inadvertently gave you bad advice. The find_saddle_points signature should actually be pub fn find_saddle_points(_: &[Vec<u64>]) -> Vec<(u64, u64)>.

I was wrong to tell you that it should accept an &[&[u64]], because the compiler doesn't automatically dereference the inner elements into a slice; the inner elements need to remain Vec<u64>. At the same time, the outer layer should be &[_] because it is idiomatic to pass a slice reference, not to transfer ownership of a Vec.

In other words, the function signature and tests should work as in this playground example.

I apologize for the bad advice. Now please change the signature one more time to hopefully clean this up once and for all.

Copy link
Member

@coriolinus coriolinus Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably the best function signature for real projects would look like this:

pub fn find_saddle_points<X, Y, T>(_: &Y) -> Vec<(usize, usize)>
where
    Y: AsRef<[X]>,
    X: AsRef<[T]>,
    T: PartialOrd,
{
    unimplemented!()
}

However, that would be an intimidating signature to require of new students. I still prefer &[Vec<u64>].

let value = input[i][j];

if value >= *max && value <= *min {
saddle_points.push((i as u64, j as u64));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, we could eliminate the casts here by having the function return Vec<(usize, usize)>. I prefer that over the current version.

coriolinus and others added 3 commits December 14, 2017 13:01
I asked for some updates to the signature two weeks ago, but
there's been no movement. In the interest of getting this exercise
into a mergeable state, I went ahead and performed the requested
updates myself:

- exercism#400 (review)
- exercism#400 (review)
Copy link
Member

@coriolinus coriolinus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it's been two weeks, I've gone ahead and made the changes I requested myself. I'm now happy with the state of this exercise.

@coriolinus coriolinus merged commit 9a2b8d9 into exercism:master Dec 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants