Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Elixir Exercism

notebook.livemd

Elixir Exercism

Exercise 1

Instructions In this exercise you’re going to write some code to help you cook a brilliant lasagna from your favorite cooking book.

You have five tasks, all related to the time spent cooking the lasagna.

  1. Define the expected oven time in minutes Define the Lasagna.expected_minutes_in_oven/0 method that does not take any arguments and returns how many minutes the lasagna should be in the oven. According to the cooking book, the expected oven time in minutes is 40:
Lasagna.expected_minutes_in_oven()
# => 40
  1. Calculate the remaining oven time in minutes Define the Lasagna.remaining_minutes_in_oven/1 method that takes the actual minutes the lasagna has been in the oven as a argument and returns how many minutes the lasagna still has to remain in the oven, based on the expected oven time in minutes from the previous task.
          Lasagna.remaining_minutes_in_oven(30)
          # => 10
  2. Calculate the preparation time in minutes Define the Lasagna.preparation_time_in_minutes/1 method that takes the number of layers you added to the lasagna as a argument and returns how many minutes you spent preparing the lasagna, assuming each layer takes you 2 minutes to prepare.
          Lasagna.preparation_time_in_minutes(2)
          # => 4
  3. Calculate the total working time in minutes Define the Lasagna.total_time_in_minutes/2 method that takes two arguments: the first argument is the number of layers you added to the lasagna, and the second argument is the number of minutes the lasagna has been in the oven. The function should return how many minutes in total you’ve worked on cooking the lasagna, which is the sum of the preparation time in minutes, and the time in minutes the lasagna has spent in the oven at the moment.
          Lasagna.total_time_in_minutes(3, 20)
          # => 26
  4. Create a notification that the lasagna is ready Define the Lasagna.alarm/0 method that does not take any arguments and returns a message indicating that the lasagna is ready to eat.
          Lasagna.alarm()
          # => "Ding!"
defmodule Lasagna do
  def alarm() do
    "Ding!"
  end

  def expected_minutes_in_oven() do
    40
  end

  def remaining_minutes_in_oven(minutes_in_oven) do
    expected_minutes_in_oven() - minutes_in_oven
  end

  def preparation_time_in_minutes(layers) do
    layers * 2
  end

  def total_time_in_minutes(layers, minutes_in_oven) do
    preparation_time_in_minutes(layers) + minutes_in_oven
  end
end

IO.inspect(Lasagna.expected_minutes_in_oven())
IO.inspect(Lasagna.remaining_minutes_in_oven(30))
IO.inspect(Lasagna.preparation_time_in_minutes(2))
IO.inspect(Lasagna.total_time_in_minutes(3, 20))

Lasagna.alarm()
40
10
4
26
"Ding!"

Exercise 2

Instructions In this exercise, you’ve been tasked with writing the software for an encryption device that works by performing transformations on data. You need a way to flexibly create complicated functions by combining simpler functions together.

For each task, return an anonymous function that can be invoked from the calling scope.

All functions should expect integer arguments. Integers are also suitable for performing bitwise operations in Elixir.

  1. Create an adder Implement Secrets.secret_add/1. It should return a function which takes one argument and adds to it the argument passed in to secret_add.

    adder = Secrets.secret_add(2)
    adder.(2)
    # => 4
  2. Create a subtractor Implement Secrets.secret_subtract/1. It should return a function which takes one argument and subtracts from it the secret passed in to secret_subtract.

    subtractor = Secrets.secret_subtract(2)
    subtractor.(3)
    # => 1
  3. Create a multiplier Implement Secrets.secret_multiply/1. It should return a function which takes one argument and multiplies it by the secret passed in to secret_multiply.

    multiplier = Secrets.secret_multiply(7)
    multiplier.(3)
    # => 21
  4. Create a divider Implement Secrets.secret_divide/1. It should return a function which takes one argument and divides it by the secret passed in to secret_divide.

    divider = Secrets.secret_divide(3)
    divider.(32)
    # => 10

    Make use of integer division so the output is compatible with the other functions’ expected input.

  5. Create an “and”-er Implement Secrets.secret_and/1. It should return a function which takes one argument and performs a bitwise and operation on it and the secret passed in to secret_and.

    ander = Secrets.secret_and(1)
    ander.(2)
    # => 0
  6. Create an “xor”-er Implement Secrets.secret_xor/1. It should return a function which takes one argument and performs a bitwise xor operation on it and the secret passed in to secret_xor.

    xorer = Secrets.secret_xor(1)
    xorer.(3)
    # => 2
  7. Create a function combiner Implement Secrets.secret_combine/2. It should return a function which takes one argument and applies to it the two functions passed in to secret_combine in order.

    multiply = Secrets.secret_multiply(7)
    divide = Secrets.secret_divide(3)
    combined = Secrets.secret_combine(multiply, divide)

combined.(6)

defmodule Secrets do
  use Bitwise

  def secret_add(num) do
    &(&1 + num)
  end

  def secret_subtract(num) do
    &(&1 - num)
  end

  def secret_multiply(num) do
    &(num * &1)
  end

  def secret_divide(num) do
    &Integer.floor_div(&1, num)
  end

  def secret_and(num) do
    &(&1 &&& num)
  end

  def secret_xor(num) do
    &Bitwise.bxor(&1, num)
  end

  def secret_combine(func_1, func_2) do
    &func_2.(func_1.(&1))
  end
end

adder = Secrets.secret_add(2)
IO.inspect(adder.(2))

subtractor = Secrets.secret_subtract(2)
IO.inspect(subtractor.(3))

multiplier = Secrets.secret_multiply(7)
IO.inspect(multiplier.(3))

divider = Secrets.secret_divide(3)
IO.inspect(divider.(32))

ander = Secrets.secret_and(1)
IO.inspect(ander.(2))

xorer = Secrets.secret_xor(1)
IO.inspect(xorer.(3))

multiply = Secrets.secret_multiply(7)
divide = Secrets.secret_divide(3)
combined = Secrets.secret_combine(multiply, divide)
IO.inspect(combined.(6))
warning: use Bitwise is deprecated. import Bitwise instead
  Desktop/Projects/elixir_exercism/notebook.livemd#cell:7tfge7pfcp4x3uf37jp56osxrdpx75wv:2: Secrets

4
1
21
10
0
2
14
14

Exercise 3

Instructions: In this exercise you’ll be writing code to help a freelancer communicate with a project manager by providing a few utilities to quickly calculate daily and monthly rates, optionally with a given discount.

We first establish a few rules between the freelancer and the project manager:

The daily rate is 8 times the hourly rate. A month has 22 billable days. The freelancer is offering to apply a discount if the project manager chooses to let the freelancer bill per month, which can come in handy if there is a certain budget the project manager has to work with.

Discounts are modeled as fractional numbers representing percentage, for example 25.0 (25%).

  1. Calculate the daily rate given an hourly rate Implement a function to calculate the daily rate given an hourly rate:

    FreelancerRates.daily_rate(60)
    # => 480.0

    The returned daily rate should be a float.

  2. Calculate a discounted price Implement a function to calculate the price after a discount.

    FreelancerRates.apply_discount(150, 10)
    # => 135.0

    The returned value should always be a float, not rounded in any way.

  3. Calculate the monthly rate, given an hourly rate and a discount Implement a function to calculate the monthly rate, and apply a discount:

    FreelancerRates.monthly_rate(77, 10.5)
    # => 12130

    The returned monthly rate should be rounded up (take the ceiling) to the nearest integer.

  4. Calculate the number of workdays given a budget, hourly rate and discount Implement a function that takes a budget, a hourly rate, and a discount, and calculates how many days of work that covers.

    FreelancerRates.days_in_budget(20000, 80, 11.0)
    # => 35.1

    The returned number of days should be rounded down (take the floor) to one decimal place.

defmodule FreelancerRates do
  def daily_rate(hourly_rate) do
    hourly_rate * 8.0
  end

  def daily_rate(hourly_rate, discount) do
    daily_rate(hourly_rate) |> apply_discount(discount)
  end

  def apply_discount(amount, discount) do
    amount * (100 - discount) / 100
  end

  def monthly_rate(hourly_rate, discount) do
    (daily_rate(hourly_rate, discount) * 22) |> ceil()
  end

  def days_in_budget(budget, hourly_rate, discount) do
    (budget / daily_rate(hourly_rate, discount)) |> Float.floor(1)
  end
end

IO.inspect(FreelancerRates.daily_rate(60))
IO.inspect(FreelancerRates.apply_discount(150, 10))
IO.inspect(FreelancerRates.monthly_rate(77, 10.5))
IO.inspect(FreelancerRates.days_in_budget(20_000, 80, 11.0))
480.0
135.0
12130
35.1
35.1

Exercise 4

Instructions In this exercise you need to implement some functions to manipulate a list of programming languages.

  1. Define a function to return an empty language list Define the new/0 function that takes no arguments and returns an empty list.
          LanguageList.new()
          # => []
  2. Define a function to add a language to the list Define the add/2 function that takes 2 arguments (a language list and a string literal of a language). It should return the resulting list with the new language prepended to the given list.
          LanguageList.new()
          |> LanguageList.add("Clojure")
          |> LanguageList.add("Haskell")
          # => ["Haskell", "Clojure"]
  3. Define a function to remove a language from the list Define the remove/1 function that takes 1 argument (a language list). It should return the list without the first item. Assume the list will always have at least one item.
          LanguageList.new()
          |> LanguageList.add("Clojure")
          |> LanguageList.add("Haskell")
          |> LanguageList.remove()
          # => ["Clojure"]
  4. Define a function to return the first item in the list Define the first/1 function that takes 1 argument (a language list). It should return the first language in the list. Assume the list will always have at least one item.
          LanguageList.new()
          |> LanguageList.add("Elm")
          |> LanguageList.add("Prolog")
          |> LanguageList.first()
          # => "Prolog"
  5. Define a function to return how many languages are in the list Define the count/1 function that takes 1 argument (a language list). It should return the number of languages in the list.
          LanguageList.new()
          |> LanguageList.add("Elm")
          |> LanguageList.add("Prolog")
          |> LanguageList.count()
          # => 2
  6. Define a function to determine if the list includes a functional language Define the functional_list?/1 function which takes 1 argument (a language list). It should return a boolean value. It should return true if “Elixir” is one of the languages in the list.
          LanguageList.new()
          |> LanguageList.add("Elixir")
          |> LanguageList.functional_list?()
          # => true
defmodule LanguageList do
  def new do
    []
  end

  def add(list, new_element) do
    [new_element | list]
  end

  def remove(list) do
    [_ | tail] = list
    tail
  end

  def first(list) do
    [head | _] = list
    head
  end

  def count(list) do
    length(list)
  end

  def functional_list?(list) do
    cond do
      count(list) == 0 -> false
      first(list) == "Elixir" -> true
      true -> functional_list?(remove(list))
    end
  end
end

LanguageList.new()
|> LanguageList.add("Elixir")
|> LanguageList.functional_list?()
true

Log Level

Instructions You are running a system that consists of a few applications producing many logs. You want to write a small program that will aggregate those logs and give them labels according to their severity level. All applications in your system use the same log codes, but some of the legacy applications don’t support all the codes.

Log code Log label Supported in legacy apps?
0 trace no
1 debug yes
2 info yes
3 warning yes
4 error yes
5 fatal no
? unknown -
  1. Return the logging code label

Implement the LogLevel.to_label/2 function. It should take an integer code and a boolean flag telling you if the log comes from a legacy app, and return the label of a log line as an atom. Unknown log codes and codes unsupported in a legacy app should return an unknown label.

LogLevel.to_label(0, false)
# => :trace

LogLevel.to_label(0, true)
# => :unknown
  1. Send an alert

Somebody has to be notified when unexpected things happen.

Implement the LogLevel.alert_recipient/2 function to determine to whom the alert needs to be sent. The function should take an integer code and a boolean flag telling you if the log comes from a legacy app, and return the name of the recipient as an atom.

If the log label is error or fatal, send the alert to the ops team. If you receive a log with an unknown label from a legacy system, send the alert to the dev1 team, other unknown labels should be sent to the dev2 team. All other log labels can be safely ignored.

LogLevel.alert_recipient(-1, true)
# => :dev1

LogLevel.alert_recipient(0, false)
# => false
false
defmodule LogLevel do
  def to_label(level, legacy?) do
    cond do
      level == 0 && legacy? == false -> :trace
      level == 1 -> :debug
      level == 2 -> :info
      level == 3 -> :warning
      level == 4 -> :error
      level == 5 && legacy? == false -> :fatal
      true -> :unknown
    end
  end

  def alert_recipient(level, legacy?) do
    label = to_label(level, legacy?)

    cond do
      label == :error || label == :fatal -> :ops
      label == :unknown && legacy? == true -> :dev1
      label == :unknown -> :dev2
      true -> false
    end

    # Please implement the alert_recipient/2 function
  end
end

LogLevel.to_label(0, false)
LogLevel.alert_recipient(-1, true)
LogLevel.alert_recipient(0, false)
LogLevel.to_label(1, false)
:debug

KitchenCalculator

Instructions

While preparing to bake cookies for your friends, you have found that you have to convert some of the measurements used in the recipe. Being only familiar with the metric system, you need to come up with a way to convert common US baking measurements to milliliters (mL) for your own ease.

Use this conversion chart for your solution:

Unit to convert volume in milliliters (mL)
ml 1 1
US cup 1 240
US fluid ounce 1 30
US teaspoon 1 5
US tablespoon 1 15

Being a talented programmer in training, you decide to use milliliters as a transition unit to facilitate the conversion from any unit listed to any other (even itself).

1. Get the numeric component from a volume-pair

Implement the KitchenCalculator.get_volume/1 function. Given a volume-pair tuple, it should return just the numeric component.

KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0

2. Convert the volume-pair to milliliters

Implement the KitchenCalculator.to_milliliter/1 function. Given a volume-pair tuple, it should convert the volume to milliliters using the conversion chart.

Use multiple function clauses and pattern matching to create the functions for each unit. The atoms used to denote each unit are: :cup, :fluid_ounce, :teaspoon, :tablespoon, :milliliter. Return the result of the conversion wrapped in a tuple.

KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}

3. Convert the milliliter volume-pair to another unit

Implement the KitchenCalculator.from_milliliter/2 function. Given a volume-pair tuple and the desired unit, it should convert the volume to the desired unit using the conversion chart.

Use multiple function clauses and pattern matching to create the functions for each unit. The atoms used to denote each unit are: :cup, :fluid_ounce, :teaspoon, :tablespoon, :milliliter

KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}

4. Convert from any unit to any unit

Implement the KitchenCalculator.convert/2 function. Given a volume-pair tuple and the desired unit, it should convert the given volume to the desired unit.

KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}
defmodule KitchenCalculator do
  def get_volume({_, volume}), do: volume

  def to_milliliter({:milliliter, value}), do: {:milliliter, value}
  def to_milliliter({:cup, value}), do: {:milliliter, value * 240}
  def to_milliliter({:fluid_ounce, value}), do: {:milliliter, value * 30}
  def to_milliliter({:teaspoon, value}), do: {:milliliter, value * 5}
  def to_milliliter({:tablespoon, value}), do: {:milliliter, value * 15}

  def from_milliliter({:milliliter, value}, :milliliter), do: {:milliliter, value}
  def from_milliliter({:milliliter, value}, :cup), do: {:cup, value / 240}
  def from_milliliter({:milliliter, value}, :fluid_ounce), do: {:fluid_ounce, value / 30}
  def from_milliliter({:milliliter, value}, :teaspoon), do: {:teaspoon, value / 5}
  def from_milliliter({:milliliter, value}, :tablespoon), do: {:tablespoon, value / 15}

  def convert({from_unit, value}, unit) do
    to_milliliter({from_unit, value})
    |> from_milliliter(unit)
  end
end

import ExUnit.Assertions

assert KitchenCalculator.get_volume({:cup, 2.0}) == 2.0
assert KitchenCalculator.to_milliliter({:cup, 2.5}) == {:milliliter, 600.0}
assert KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup) == {:cup, 5.5}
assert KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon) == {:tablespoon, 3.0}
true

High School Sweetheart

Instructions

In this exercise, you are going to help high school sweethearts profess their love on social media by generating an ASCII heart with their initials:

     ******       ******
   **      **   **      **
 **         ** **         **
**            *            **
**                         **
**     J. K.  +  M. B.     **
 **                       **
   **                   **
     **               **
       **           **
         **       **
           **   **
             ***
              *

1. Get the name’s first letter

Implement the HighSchoolSweetheart.first_letter/1 function. It should take a name and return its first letter. It should clean up any unnecessary whitespace from the name.

HighSchoolSweetheart.first_letter("Jane")
# => "J"

2. Format the first letter as an initial

Implement the HighSchoolSweetheart.initial/1 function. It should take a name and return its first letter, uppercase, followed by a dot. Make sure to reuse HighSchoolSweetheart.first_letter/1 that you defined in the previous step.

HighSchoolSweetheart.initial("Robert")
# => "R."

3. Split the full name into the first name and the last name

Implement the HighSchoolSweetheart.initials/1 function. It should take a full name, consisting of a first name and a last name separated by a space, and return the initials. Make sure to reuse HighSchoolSweetheart.initial/1 that you defined in the previous step.

HighSchoolSweetheart.initials("Lance Green")
# => "L. G."

4. Put the initials inside of the heart

Implement the HighSchoolSweetheart.pair/2 function. It should take two full names and return the initials. Make sure to reuse HighSchoolSweetheart.initials/1 that you defined in the previous step.

HighSchoolSweetheart.pair("Blake Miller", "Riley Lewis")
# => """
#      ******       ******
#    **      **   **      **
#  **         ** **         **
# **            *            **
# **                         **
# **     B. M.  +  R. L.     **
#  **                       **
#    **                   **
#      **               **
#        **           **
#          **       **
#            **   **
#              ***
#               *
# """
defmodule HighSchoolSweetheart do
  def first_letter(name),
    do: name |> String.trim() |> String.first()

  def initial(name),
    do: first_letter(name) |> String.upcase() |> Kernel.<>(".")

  def initials(full_name) do
    full_name
    |> String.split()
    |> Enum.map(&amp;initial/1)
    |> Enum.join(" ")
  end

  def pair(full_name1, full_name2) do
    n1 = initials(full_name1)
    n2 = initials(full_name2)

    """
         ******       ******
       **      **   **      **
     **         ** **         **
    **            *            **
    **                         **
    **     #{n1}  +  #{n2}     **
     **                       **
       **                   **
         **               **
           **           **
             **       **
               **   **
                 ***
                  *
    """
  end
end

import ExUnit.Assertions

assert HighSchoolSweetheart.first_letter("Jane") == "J"
assert HighSchoolSweetheart.initial("Robert") == "R."
assert HighSchoolSweetheart.initials("Lance Green") == "L. G."

heart_assertion = """
     ******       ******
   **      **   **      **
 **         ** **         **
**            *            **
**                         **
**     B. M.  +  R. L.     **
 **                       **
   **                   **
     **               **
       **           **
         **       **
           **   **
             ***
              *
"""

assert HighSchoolSweetheart.pair("Blake Miller", "Riley Lewis") == heart_assertion
true