Skip to content

Latest commit

 

History

History
262 lines (201 loc) · 7.72 KB

saferange.livemd

File metadata and controls

262 lines (201 loc) · 7.72 KB

SafeRange

Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.9", override: true},
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"}
])

Navigation

A Safe Range Function

This is a guest post created by Quentin Crain and inspired by Jeff Helman, one of our fantastic beta-testers.

In the lesson on creating a range of numbers using the .. operator:

iex > Enum.to_list(1..5)
[1, 2, 3, 4, 5]

You also learned that it can optionally take a step value:

iex > Enum.to_list(1..5//2)
[1, 3, 5]

In this way, the programmer may create a sequence of integers starting at the first number and returning increments by step until the last number is reached or exceeded.

But, what happens when the step attempts to create a sequence that is not incrementing towards the last number, such as:

iex> Enum.to_list(1..5//-1)
[]

In this case, an empty list ([]) is returned as there are no numbers between 1 and 5 starting at 1 and incrementing by -1.

As was brought up* in a Beta release of this course, why would Elixir allow such code? Does it compile? Does it evaluate?

In this bonus exercise, you will be recreating the range function AND creating a safe version.

In Elixir, there is already a standard on providing safe versions of functions: When you want Elixir to throw an exception instead of returning a reasonable or error value, the name of the function will end with !.

Two examples from the standard library may be referred to:

Enum.fetch & Enum.fetch!

Enum.html

The Enum module provides two functions to retrieve an element from a list ([]) at a given index. The difference is in how they behave when the index is invalid; ex:

iex> list = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> Enum.fetch(list, 10)
:error
iex> Enum.fetch!(list, 10)
** (Enum.OutOfBoundsError) out of bounds error
    (elixir 1.14.0) lib/enum.ex:1072: Enum.fetch!/2
    iex:27: (file)

Map.fetch & Map.fetch!

Map.html

When accessing a Map (%{}) with a key, the Map module provides these two functions. The difference is in how they behave when the key is not present in the map; ex:

iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
iex> Map.fetch(map, :c)
:error
iex> Map.fetch!(map, :c)
** (KeyError) key :c not found in: %{a: 1, b: 2}
    (stdlib 4.0.1) :maps.get(:c, %{a: 1, b: 2})
    iex:29: (file)

Your assignment is to implement the SafeRange module with 2 functions called range and range! where:

  • SafeRange.range behaves as the existing ..//STEP function

  • SafeRange.range! verifies that the given step "makes sense" with the given first and last values, and if not a RuntimeError exception is thrown.

In Elixir exceptions may be thrown using the raise function, for example here is how to throw a RuntimeError exception:

iex> raise "Houston, we have a problem."
** (RuntimeError) Houston, we have a problem.
defmodule SafeRange do
  @doc """
  Generate a sequences of integers from 'first' to 'last' with an
  option 'step' value.

  If incrementing by 'step' will not end at or beyond 'last',
  just return an empty list ([]).

  ## Examples

    iex> SafeRange.range(1, 5)
    [1, 2, 3, 4, 5]

    iex> SafeRange.range(1, 5, 2)
    [1, 3, 5]

    iex> SafeRange.range(1, 5, -1)
    []

    iex> SafeRange.range(5, 1, 1)
    []
  """
  def range(first, last, step \\ DEFAULT) do
  end

  @doc """
  Generate a sequences of integers from 'first' to 'last' with an
  option 'step' value.

  If incrementing by 'step' will not end at or beyond 'last',
  a "RuntimeError" will be thrown.

  ## Examples

    iex> SafeRange.range!(1, 5)
    [1, 2, 3, 4, 5]

    iex> SafeRange.range!(1, 5, 2)
    [1, 3, 5]

    iex> SafeRange.range!(1, 5, -1)
    ** (RuntimeError) Invalid step value of: -1

    iex> SafeRange.range!(5, 1, 1)
    ** (RuntimeError) Invalid step value of: 1
  """
  def range!(first, last, step \\ DEFAULT) do
  end
end
Example Solution
defmodule SafeRange do
  def range(first, last, step \\ 1) do
    Enum.to_list(first..last//step)
  end

  def range!(first, last, step \\ 1) do
    cond do
      step < 0 and first < last ->
        raise "Invalid step value of: #{step}"
      step > 0 and first > last ->
        raise "Invalid step value of: #{step}"
      true -> Enum.to_list(first..last//step)
    end
  end
end

Commit Your Progress

DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.

Run git status to ensure there are no undesirable changes. Then run the following in your command line from the curriculum folder to commit your progress.

$ git add .
$ git commit -m "finish SafeRange exercise"
$ git push

We're proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.

We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.

Navigation