Avatar of JPRoland

JPRoland's solution

to Triangle in the Rust Track

Published at Oct 19 2019 · 0 comments
Instructions
Test suite
Solution

Determine if a triangle is equilateral, isosceles, or scalene.

An equilateral triangle has all three sides the same length.

An isosceles triangle has at least two sides the same length. (It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.)

A scalene triangle has all sides of different lengths.

Note

For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. See Triangle Inequality.

Dig Deeper

The case where the sum of the lengths of two sides equals that of the third is known as a degenerate triangle - it has zero area and looks like a single line. Feel free to add your own code/tests to check for degenerate triangles.

Triangle in Rust

Implementation of this can take many forms. Here are some topics that may help you, depending on the approach you take.

Or maybe you will come up with an approach that uses none of those!

Non-integer lengths

The base exercise tests identification of triangles whose sides are all integers. However, some triangles cannot be represented by pure integers. A simple example is a right triangle (an isosceles triangle whose equal sides are separated by 90 degrees) whose equal sides both have length of 1. Its hypotenuse is the square root of 2, which is an irrational number: no simple multiplication can represent this number as an integer.

It would be tedious to rewrite the analysis functions to handle both integer and floating-point cases, and particularly tedious to do so for all potential integer and floating point types: given signed and unsigned variants of bitwidths 8, 16, 32, 64, and 128, that would be 10 reimplementations of fundamentally the same code even before considering floats!

There's a better way: generics. By rewriting your Triangle as a Triangle<T>, you can write your code once, and hand off the work of generating all those specializations to the compiler. Note that in order to use mathematical operations, you'll need to constrain your generic type to types which support those operations using traits.

There are some bonus tests you can run which test your implementation on floating-point numbers. To enable them, run your tests with the generic feature flag, like this:

cargo test --features generic

Rust Installation

Refer to the exercism help page for Rust installation and learning resources.

Writing the Code

Execute the tests with:

$ cargo test

All but the first test have been ignored. After you get the first test to pass, open the tests source file which is located in the tests directory and remove the #[ignore] flag from the next test and get the tests to pass again. Each separate test is a function with #[test] flag above it. Continue, until you pass every test.

If you wish to run all ignored tests without editing the tests source file, use:

$ cargo test -- --ignored

To run a specific test, for example some_test, you can use:

$ cargo test some_test

If the specific test is ignored use:

$ cargo test some_test -- --ignored

To learn more about Rust tests refer to the online test documentation

Make sure to read the Modules chapter if you haven't already, it will help you with organizing your files.

Further improvements

After you have solved the exercise, please consider using the additional utilities, described in the installation guide, to further refine your final solution.

To format your solution, inside the solution directory use

cargo fmt

To see, if your solution contains some common ineffective use cases, inside the solution directory use

cargo clippy --all-targets

Submitting the solution

Generally you should submit all files in which you implemented your solution (src/lib.rs in most cases). If you are using any external crates, please consider submitting the Cargo.toml file. This will make the review process faster and clearer.

Feedback, Issues, Pull Requests

The 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.

Source

The Ruby Koans triangle project, parts 1 & 2 http://rubykoans.com

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

triangle.rs

use triangle::*;

#[test]
fn positive_length_sides_are_ok() {
    let sides = [2, 2, 2];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_some());
}

#[test]
#[ignore]
fn zero_length_sides_are_illegal() {
    let sides = [0, 0, 0];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
fn one_length_zero_side_first() {
    let sides = [0, 2, 2];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
fn one_length_zero_side_second() {
    let sides = [2, 0, 2];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
fn one_length_zero_side_third() {
    let sides = [2, 2, 0];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
fn equilateral_triangles_have_equal_sides() {
    let sides = [2, 2, 2];
    let triangle = Triangle::build(sides).unwrap();
    assert!(triangle.is_equilateral());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
fn larger_equilateral_triangles_have_equal_sides() {
    let sides = [10, 10, 10];
    let triangle = Triangle::build(sides).unwrap();
    assert!(triangle.is_equilateral());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
fn isosceles_triangles_have_two_equal_sides_one() {
    let sides = [3, 4, 4];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(triangle.is_isosceles());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
fn isosceles_triangles_have_two_equal_sides_two() {
    let sides = [4, 4, 3];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(triangle.is_isosceles());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
fn isosceles_triangles_have_two_equal_sides_three() {
    let sides = [4, 3, 4];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(triangle.is_isosceles());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
fn isosceles_triangles_have_two_equal_sides_four() {
    let sides = [4, 7, 4];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(triangle.is_isosceles());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
fn scalene_triangle_has_no_equal_sides_one() {
    let sides = [3, 4, 5];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(!triangle.is_isosceles());
    assert!(triangle.is_scalene());
}

#[test]
#[ignore]
fn scalene_triangle_has_no_equal_sides_two() {
    let sides = [5, 4, 6];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(!triangle.is_isosceles());
    assert!(triangle.is_scalene());
}

#[test]
#[ignore]
fn scalene_triangle_has_no_equal_sides_three() {
    let sides = [10, 11, 12];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(!triangle.is_isosceles());
    assert!(triangle.is_scalene());
}

#[test]
#[ignore]
fn scalene_triangle_has_no_equal_sides_four() {
    let sides = [5, 4, 2];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(!triangle.is_isosceles());
    assert!(triangle.is_scalene());
}

#[test]
#[ignore]
fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_one() {
    let sides = [7, 3, 2];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_two() {
    let sides = [1, 1, 3];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
#[cfg(feature = "generic")]
fn scalene_triangle_with_floating_point_sides() {
    let sides = [0.4, 0.6, 0.3];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(!triangle.is_isosceles());
    assert!(triangle.is_scalene());
}

#[test]
#[ignore]
#[cfg(feature = "generic")]
fn equilateral_triangles_with_floating_point_sides() {
    let sides = [0.2, 0.2, 0.2];
    let triangle = Triangle::build(sides).unwrap();
    assert!(triangle.is_equilateral());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
#[cfg(feature = "generic")]
fn isosceles_triangle_with_floating_point_sides() {
    let sides = [0.3, 0.4, 0.4];
    let triangle = Triangle::build(sides).unwrap();
    assert!(!triangle.is_equilateral());
    assert!(triangle.is_isosceles());
    assert!(!triangle.is_scalene());
}

#[test]
#[ignore]
#[cfg(feature = "generic")]
fn invalid_triangle_with_floating_point_sides_one() {
    let sides = [0.0, 0.4, 0.3];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}

#[test]
#[ignore]
#[cfg(feature = "generic")]
fn invalid_triangle_with_floating_point_sides_two() {
    let sides = [0.1, 0.3, 0.5];
    let triangle = Triangle::build(sides);
    assert!(triangle.is_none());
}
extern crate num_traits;

use num_traits::Num;

pub struct Triangle<T>
where
    T: Num + PartialOrd + Copy,
{
    a: T,
    b: T,
    c: T,
}

impl<T> Triangle<T>
where
    T: Num + PartialOrd + Copy,
{
    pub fn build(sides: [T; 3]) -> Option<Triangle<T>> {
        let mut sides: Vec<T> = sides.to_vec();
        sides.sort_by(|a, b| a.partial_cmp(b).unwrap());

        if sides[0] + sides[1] <= sides[2] {
            return None;
        }

        Some(Triangle {
            a: sides[0],
            b: sides[1],
            c: sides[2],
        })
    }

    pub fn is_equilateral(&self) -> bool {
        self.a == self.b && self.a == self.c
    }

    pub fn is_scalene(&self) -> bool {
        !self.is_equilateral() && !self.is_isosceles()
    }

    pub fn is_isosceles(&self) -> bool {
        self.a == self.b || self.b == self.c
    }
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?