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.
- 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
-
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
-
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
-
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
-
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.
-
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
-
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
-
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
-
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.
-
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
-
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
-
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%).
-
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.
-
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.
-
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.
-
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.
-
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() # => []
-
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"]
-
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"]
-
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"
-
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
-
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 | - |
- 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
- 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(&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