3
\$\begingroup\$

The exercise 1.3 of the book Structure and Interpretation of Computer Programs asks the following:

Exercise 1.3. Define a procedure that takes three numbers as arguments and returns the sum of the squares of the two larger numbers.

My answer is this:

#![feature(core)]
use std::cmp;

fn sum_square_largest(x:f64, y:f64, z:f64) -> f64 {
    match partial_min_three(x, y, z) {
        Some(a) =>  x * x + y * y + z * z - a * a,
        None => 0.0_f64/0.0_f64
    }
}

fn partial_min_three<T>(v1: T, v2: T, v3: T) -> Option<T> where T: PartialOrd<T> {
    match cmp::partial_min(v2, v3) {
        Some(x) => cmp::partial_min(v1, x),
        None => None
    }
}

Rust is the language I know less, I would really appreciate your advice.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I find it always pays to be tidy and conscientious about spacing. Here, I'd add spaces after the : in the argument declaration

// fn sum_square_largest(x:f64, y:f64, z:f64) -> f64 {
fn sum_square_largest(x: f64, y: f64, z: f64) -> f64 {

Next, I'd wrap the where clause onto the next line. where clauses often get large, so I find it best to let them have breathing room, and only put one type per line:

// fn partial_min_three<T>(v1: T, v2: T, v3: T) -> Option<T> where T: PartialOrd<T> {
fn partial_min_three<T>(v1: T, v2: T, v3: T) -> Option<T>
    where T: PartialOrd<T>
{

Checking for Some or None and doing something is a common pattern. In this case, you can use Option::and_then:

// match partial_min(v2, v3) {
//     Some(x) => partial_min(v1, x),
//     None => None
// }
partial_min(v2, v3).and_then(|x| partial_min(v1, x))

Programming is often about clarity in expressing your intentions, and I doubt that the majority of programmers immediately know what 0 / 0 is for floating point numbers. You should be explicit:

// None => 0.0_f64/0.0_f64
None => std::f64::NAN,

Again, we can use existing patterns to deal with checking for Some / None. This time, we can use Option::map:

// match partial_min_three(x, y, z) {
//     Some(a) => x * x + y * y + z * z - a * a,
//     None => std::f64::NAN,
// } 
partial_min_three(x, y, z)
    .map(|a| x * x + y * y + z * z - a * a)
    .unwrap_or(std::f64::NAN)

I disagree with your choice of NaN as a magic value. Magic values drive me crazy, and Rust has great choices for avoiding them - Option and Result!

fn sum_square_largest(x: f64, y: f64, z: f64) -> Option<f64> {
    partial_min_three(x, y, z)
        .map(|a| x * x + y * y + z * z - a * a)
}

I'd probably create a tiny function for squaring. It's likely to get inlined, so I'm not worried about performance, just readability:

.map(|a| square(x) + square(y) + square(z) - square(a))

All together (playpen):

fn sum_square_largest(x: f64, y: f64, z: f64) -> Option<f64> {
    partial_min_three(x, y, z)
        .map(|a| square(x) + square(y) + square(z) - square(a))
}

fn partial_min_three<T>(v1: T, v2: T, v3: T) -> Option<T>
    where T: PartialOrd<T>
{
    partial_min(v2, v3).and_then(|x| partial_min(v1, x))
}

fn square(x: f64) -> f64 { x * x }

// Copied from the standard library as it is currently unstable
// and the beta doesn't allow unstable features
use std::cmp::Ordering;
fn partial_min<T: PartialOrd>(v1: T, v2: T) -> Option<T> {
    match v1.partial_cmp(&v2) {
        Some(Ordering::Less) | Some(Ordering::Equal) => Some(v1),
        Some(Ordering::Greater) => Some(v2),
        None => None
    }
}

fn main() {
    let z = sum_square_largest(1.0, 2.0, 3.0);
    println!("{:?}", z);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.