Triangle - Exercism Elixir Solution & Tutorial


Percy Grunwald's Profile Picture

Written by Percy Grunwald

— Last Updated February 22, 2019

Today’s problem was to implement a function that can validate and determine the type of a triangle based on side lengths passed in as arguments. For example, (1, 1, 1) should tell us that this is a valid triangle and of type equilateral.

Live solution video

This is my daily live stream video solution to this problem.

Explanation of the solution

Functions to determine whether a triangle is equilateral, isosceles or scalene

It’s fairly straightforward to implement functions to determine whether a triangle is equilateral, isosceles or scalene. All of them are one-liners using simple equality comparison operators and some boolean and/or.

Equilateral triangles have 3 sides of equal length:

defp is_equilateral?(a, b, c) do
  a == b and b == c
end

Isosceles triangles have 2 sides of equal length:

defp is_isosceles?(a, b, c) do
  a == b or a == c or b == c
end

Scalene triangles have 3 sides of different lengths:

defp is_scalene?(a, b, c) do
  a != b and a != c and b != c
end

Validation: are all side lengths positive?

Part of the validation we needed to implement was to check if all the sides of the triangle are positive - i.e. greater than 0. This is also easy to implement as a one-liner:

defp all_side_lengths_positive?(a, b, c) do
  a > 0 and b > 0 and c > 0
end

Validation: does the triangle meet the triangle inequality?

This validation is a little more complex. The triangle inequality states that for all triangles with non-zero area (i.e. other than a line) each side must be less than the sum of the other sides.

This makes sense, because if any side is equal to the sum of the other sides, it means that the triangle is just two lines end to end. If any side is greater than the sum of the other sides, we’re no longer talking about a triangle at all, since two lines end to end can’t be longer than the sum of their lengths.

Initially, I went down a path of trying to implement a function to get the longest side and compare it to the sum of the other 2 sides. While working on this function, Twitch user SegFaultAx helpfully pointed out in Twitch chat that this function can also be implemented as a combination of inequalities:

defp triangle_meets_triangle_inequality?(a, b, c) do
  b + c > a and a + c > b and a + b > c
end

Main function to tie everything together

Now that we have all the functions to validate and categorize the triangle, we just need to tie everything together.

I opted to use a cond statement since I think it’s the most readable way to express the flow of this function:

@doc """
Return the kind of triangle of a triangle with 'a', 'b' and 'c' as lengths.
"""
@spec kind(number, number, number) :: {:ok, kind} | {:error, String.t()}
def kind(a, b, c) do
  cond do
    not all_side_lengths_positive?(a, b, c) ->
      {:error, "all side lengths must be positive"}

    not triangle_meets_triangle_inequality?(a, b, c) ->
      {:error, "side lengths violate triangle inequality"}

    is_equilateral?(a, b, c) ->
      {:ok, :equilateral}

    is_isosceles?(a, b, c) ->
      {:ok, :isosceles}

    is_scalene?(a, b, c) ->
      {:ok, :scalene}

    true ->
      {:error, "Not a valid triangle"}
  end
end

Full solution in text form

Here’s the full solution in text form, in case you want to look over the final product:

defmodule Triangle do
  @type kind :: :equilateral | :isosceles | :scalene

  @doc """
  Return the kind of triangle of a triangle with 'a', 'b' and 'c' as lengths.
  """
  @spec kind(number, number, number) :: {:ok, kind} | {:error, String.t()}
  def kind(a, b, c) do
    cond do
      not all_side_lengths_positive?(a, b, c) ->
        {:error, "all side lengths must be positive"}

      not triangle_meets_triangle_inequality?(a, b, c) ->
        {:error, "side lengths violate triangle inequality"}

      is_equilateral?(a, b, c) ->
        {:ok, :equilateral}

      is_isosceles?(a, b, c) ->
        {:ok, :isosceles}

      is_scalene?(a, b, c) ->
        {:ok, :scalene}

      true ->
        {:error, "Not a valid triangle"}
    end
  end

  defp all_side_lengths_positive?(a, b, c) do
    a > 0 and b > 0 and c > 0
  end

  defp triangle_meets_triangle_inequality?(a, b, c) do
    b + c > a and a + c > b and a + b > c
  end

  defp is_equilateral?(a, b, c) do
    a == b and b == c
  end

  defp is_isosceles?(a, b, c) do
    a == b or a == c or b == c
  end

  defp is_scalene?(a, b, c) do
    a != b and a != c and b != c
  end
end

Comments