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

Elixir Exersices from Exercism

exersicm.livemd

Elixir Exersices from Exercism

Freelancer Rates

Description Link

Solutions

defmodule FreelancerRates do
  def daily_rate(hourly_rate), do: hourly_rate * 8

  def apply_discount(total_rate, discount) do
    total_rate - percentage(total_rate, discount)
  end

  def monthly_rate(hourly_rate, discount) do
    daily_rate(hourly_rate)
    |> Kernel.*(22)
    |> apply_discount(discount)
    |> Float.ceil()
    |> trunc()
  end

  def days_in_budget(budget, hourly_rate, discount) do
    daily_rate =
      hourly_rate
      |> daily_rate()
      |> apply_discount(discount)

    Float.floor(budget / daily_rate, 1)
  end

  defp percentage(total_rate, discount), do: total_rate * discount / 100
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(20000, 80, 11.0))

Language List

Description Link

Solutions

defmodule LanguageList do
  def new(), do: []

  def add(language_list, language) do
    [language | language_list]
  end

  def remove([_head | tail]), do: tail

  def first([head | _tail]), do: head

  def count(language_list), do: length(language_list)

  def function_list?(language_list), do: "Elixir" in language_list
end
LanguageList.new()
|> LanguageList.add("Erlang")
|> LanguageList.add("Elixir")
|> LanguageList.add("Clojure")
|> LanguageList.remove()
# |> LanguageList.first()
# |> LanguageList.count()
|> LanguageList.function_list?()

Secrets

Description link

Solutions

defmodule Secrets do
  import Bitwise

  def secret_add(arg1), do: &(&1 + arg1)

  def secret_substract(arg1), do: &(&1 - arg1)

  def secret_multiply(arg1), do: &(&1 * arg1)

  def secret_divide(arg1), do: &div(&1, arg1)

  def secret_and(arg1), do: &(&1 &&& arg1)

  def secret_xor(arg1), do: &bxor(&1, arg1)

  def secret_combine(fun1, fun2) do
    fn arg ->
      arg
      |> fun1.()
      |> fun2.()
    end
  end
end
adder = Secrets.secret_add(2)
adder.(2)

subtractor = Secrets.secret_substract(2)
subtractor.(3)

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

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

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

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

combined = Secrets.secret_combine(multiplier, divider)
combined.(6)

Log Level

Description Link

Solutions

defmodule LogLevel do
  @log_codes %{
    0 => %{:log_label => :trace, :legacy_supported? => false},
    1 => %{:log_label => :debug, :legacy_supported? => true},
    2 => %{:log_label => :info, :legacy_supported? => true},
    3 => %{:log_label => :warning, :legacy_supported? => true},
    4 => %{:log_label => :error, :legacy_supported? => true},
    5 => %{:log_label => :fatal, :legacy_supported? => false}
  }

  def to_label(code, is_legacy?) do
    legacy_supported? = get_in(@log_codes, [code, :legacy_supported?])

    cond do
      legacy_supported? -> @log_codes[code].log_label
      legacy_supported? == false and !is_legacy? -> @log_codes[code].log_label
      true -> :unknown
    end
  end

  def alert_recipient(code, is_legacy?) do
    cond do
      to_label(code, is_legacy?) in [:error, :fatal] -> :ops
      is_legacy? == true and to_label(code, is_legacy?) == :unknown -> :dev1
      to_label(code, is_legacy?) == :unknown -> :dev2
      true -> false
    end
  end
end
LogLevel.to_label(0, false)
LogLevel.alert_recipient(-1, true)
LogLevel.alert_recipient(0, false)

Guessing Game

Description Link

Solution

defmodule GuessingGame do
  def compare(secret_number, guess \\ :no_guess)

  def compare(_secret_number, :no_guess), do: "Make a guess"

  def compare(secret_number, secret_number), do: "Correct"

  def compare(secret_number, guess) when secret_number in [guess + 1, guess - 1], do: "So close"

  def compare(secret_number, guess) when guess > secret_number, do: "Too high"

  def compare(secret_number, guess) when guess < secret_number, do: "Too low"
end
GuessingGame.compare(5, 5) |> IO.inspect()
GuessingGame.compare(5, 8) |> IO.inspect()
GuessingGame.compare(5, 2) |> IO.inspect()
GuessingGame.compare(5, 6) |> IO.inspect()
GuessingGame.compare(5) |> IO.inspect()
GuessingGame.compare(5, :no_guess) |> IO.inspect()

Kitchen Calculator

Description Link

Solution

defmodule KitchenCalculator do
  def get_volume({_unit, volume}), do: volume

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

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

  def convert(volume_tuple, target_unit) do
    volume_tuple
    |> to_milliliter()
    |> from_milliliter(target_unit)
  end
end
KitchenCalculator.get_volume({:cup, 2.0}) |> IO.inspect()
KitchenCalculator.to_milliliter({:cup, 2.5}) |> IO.inspect()
KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup) |> IO.inspect()
KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)

High School Sweetheart

Description Link

Solution

defmodule HighSchoolSweetheart do
  def first_letter(name) do
    name
    |> String.trim()
    |> String.first()
  end

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

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

  def pair(full_name1, full_name2) do
    """
          ******       ******
        **      **   **      **
      **         ** **         **
      **           *            **
      **                         **
      **     #{initials(full_name1)}  +  #{initials(full_name2)}     **
      **                       **
        **                   **
          **               **
            **           **
              **       **
                **   **
                  ***
                   *
    """
  end
end
HighSchoolSweetheart.first_letter("Jane")
HighSchoolSweetheart.initial("robert")
HighSchoolSweetheart.initials("Lance Green")
HighSchoolSweetheart.pair("Blake Miller", "Riley Lewis") |> IO.inspect()

Bird Count

Description Link

Solution

# 15.43
defmodule BirdCount do
  def today([]), do: nil
  def today([today | _]), do: today

  def increment_day_count([]), do: [1]
  def increment_day_count([today | tail]), do: [today + 1 | tail]

  def has_day_without_birds?([]), do: false
  def has_day_without_birds?([0 | _tail]), do: true
  def has_day_without_birds?([_ | tail]), do: has_day_without_birds?(tail)

  def total([]), do: 0
  def total([total]), do: total
  def total([total, daily_count | tail]), do: total([total + daily_count | tail])

  def busy_days(daily_counts), do: busy_days(daily_counts, 0)

  def busy_days([], busy_days), do: busy_days

  def busy_days([daily_count | tail], busy_days) when daily_count >= 5,
    do: busy_days(tail, busy_days + 1)

  def busy_days([_daily_count | tail], busy_days), do: busy_days(tail, busy_days)
end
BirdCount.today([2, 5, 1]) |> IO.inspect()
BirdCount.increment_day_count([4, 0, 2]) |> IO.inspect()
BirdCount.has_day_without_birds?([2, 0, 4]) |> IO.inspect()
BirdCount.has_day_without_birds?([3, 8, 1, 5]) |> IO.inspect()
BirdCount.total([4, 0, 9, 0, 5]) |> IO.inspect()
BirdCount.busy_days([4, 5, 0, 0, 6]) |> IO.inspect()

High Score

Description Link

Solution

defmodule HighScore do
  @base_score 0

  def new(), do: %{}

  def add_player(scores, player_name, score \\ @base_score),
    do: Map.put(scores, player_name, score)

  def remove_player(scores, player_name), do: Map.delete(scores, player_name)

  def reset_score(scores, player_name), do: Map.put(scores, player_name, @base_score)

  def update_score(scores, player_name, score),
    do: Map.update(scores, player_name, score, &amp;(&amp;1 + score))

  def get_players(scores), do: Map.keys(scores)
end
HighScore.new()
|> HighScore.add_player("José Valim", 486_373)
|> HighScore.add_player("Euen")
|> HighScore.update_score("José Valim", 7)
|> HighScore.reset_score("José Valim")
|> HighScore.reset_score("Ann")
|> HighScore.update_score("Andrea", 459)
|> HighScore.get_players()
|> dbg()

City Office

Description Link

Solution

defmodule Form do
  @moduledoc """
  A collection of loosely related functions helpful for filling out various forms at the city office.
  """

  @doc """
  Generates a string of a given length.

  This string can be used to fill out a form field that is supposed to have no value.
  Such fields cannot be left empty because a malicious third party could fill them out with false data.
  """

  @type address_map :: %{:street => String.t(), :postal_code => String.t(), :city => String.t()}

  @type address_tuple :: {street :: String.t(), postal_code :: String.t(), city :: String.t()}

  @type address :: address_map() | address_tuple()

  @spec blanks(non_neg_integer()) :: String.t()
  def blanks(n) do
    String.duplicate("X", n)
  end

  @doc """
  Splits the string into a list of uppercase letters.

  This is needed for form fields that don't offer a single input for the whole string,
  but instead require splitting the string into a predefined number of single-letter inputs.
  """
  @spec letters(String.t()) :: [String.t()]
  def letters(word) do
    word
    |> String.upcase()
    |> String.split("", trim: true)
  end

  @doc """
  Checks if the value has no more than the maximum allowed number of letters.

  This is needed to check that the values of fields do not exceed the maximum allowed length.
  It also tells you by how much the value exceeds the maximum.
  """
  @spec check_length(String.t(), non_neg_integer()) :: :ok | {:error, pos_integer()}
  def check_length(word, length) do
    diff = String.length(word) - length

    if diff <= 0 do
      :ok
    else
      {:error, diff}
    end
  end

  @doc """
  Formats the address as an uppercase multiline string.
  """
  @spec format_address(address()) :: String.t()
  def format_address(%{street: street, postal_code: postal_code, city: city}) do
    format_address({street, postal_code, city})
  end

  def format_address({street, postal_code, city}) do
    """
    #{String.upcase(street)}
    #{String.upcase(postal_code)} #{String.upcase(city)}
    """
  end
end

German Sysadmin

Description Link

Solution

defmodule Username do
  def sanitize(username), do: sanitize(username, [])

  defp sanitize(username, sanitized) do
    case username do
      [] ->
        sanitized

      [ | rest] ->
        sanitize(rest, sanitized ++ ~c"ae")

      [ | rest] ->
        sanitize(rest, sanitized ++ ~c"oe")

      [ | rest] ->
        sanitize(rest, sanitized ++ ~c"ue")

      [ | rest] ->
        sanitize(rest, sanitized ++ ~c"ss")

      [?_ | rest] ->
        sanitize(rest, sanitized ++ ~c"_")

      [letter | rest] when letter in ?a..?z ->
        sanitize(rest, sanitized ++ [letter])

      [_letter | rest] ->
        sanitize(rest, sanitized)
    end
  end
end
Username.sanitize(~c"schmidt1985")
Username.sanitize(~c"mark_fischer$$$")
Username.sanitize(~c"cäcilie_weiß")

RPG Character Sheet

Description Link

Solution

defmodule RPG.CharacterSheet do
  def welcome() do
    IO.puts("Welcome! Let's fill out your character sheet together.")
  end

  def ask_name() do
    IO.gets("What is your character's name?\n") |> String.trim()
  end

  def ask_class() do
    IO.gets("What is your character's class?\n")
  end

  def ask_level() do
    IO.gets("What is your character's level?\n")
  end

  def run() do
    welcome()
    name = ask_name()
    class = ask_class()
    level = ask_level()
    character_sheet = %{class: class, level: level, name: name}
    IO.inspect(character_sheet, label: "Your character")
  end
end
# RPG.CharacterSheet.welcome()
# RPG.CharacterSheet.ask_name()
String.trim("33\n") |> String.to_integer()

Names Badge

Description Link

Solution

defmodule NameBadge do
  def print(id, name, department) do
    id = if is_nil(id), do: "", else: "[#{id}] - "

    department =
      if is_nil(department) do
        "OWNER"
      else
        String.upcase(department)
      end

    id <> "#{name} - #{department}"
  end
end
NameBadge.print(67, "Katherine Williams", "Strategic Communication") |> IO.inspect()
NameBadge.print(nil, "Robert Johnson", nil) |> IO.inspect()

Take-A-Number

Description Link

Solution

defmodule TakeANumber do
  # def start(), do: spawn(fn -> loop(0) end)
  def start(), do: spawn(&amp;loop/0)

  def loop(state \\ 0) do
    receive do
      {:report_state, sender_pid} ->
        send(sender_pid, state)
        loop(state)

      {:take_a_number, sender_pid} ->
        new_state = state + 1
        send(sender_pid, new_state)
        loop(new_state)

      :stop ->
        :ok

      _ ->
        loop(state)
    end
  end
end
TakeANumber.start()

Wine Cellar

Description Link

Solution

defmodule WineCellar do
  def explain_colors() do
    [
      white: "Fermented without skin contact.",
      red: "Fermented with skin contact using dark-colored grapes.",
      rose: "Fermented with some skin contact, but not enough to qualify as a red wine."
    ]
  end

  def filter(cellar, color, opts \\ []) do
    wines = Keyword.get_values(cellar, color)

    wines = if opts[:year], do: filter_by_year(wines, opts[:year]), else: wines
    if opts[:country], do: filter_by_country(wines, opts[:country]), else: wines
  end

  # The functions below do not need to be modified.

  defp filter_by_year(wines, year)
  defp filter_by_year([], _year), do: []

  defp filter_by_year([{_, year, _} = wine | tail], year) do
    [wine | filter_by_year(tail, year)]
  end

  defp filter_by_year([{_, _, _} | tail], year) do
    filter_by_year(tail, year)
  end

  defp filter_by_country(wines, country)
  defp filter_by_country([], _country), do: []

  defp filter_by_country([{_, _, country} = wine | tail], country) do
    [wine | filter_by_country(tail, country)]
  end

  defp filter_by_country([{_, _, _} | tail], country) do
    filter_by_country(tail, country)
  end
end
WineCellar.filter(
  [
    white: {"Chardonnay", 2017, "Italy"},
    white: {"Pinot grigio", 2017, "Germany"},
    red: {"Pinot noir", 2016, "France"},
    rose: {"Dornfelder", 2018, "Germany"}
  ],
  :white,
  country: "Germany"
)

Paint by Number

Link Description

Solution

defmodule PaintByNumber do
  def palette_bit_size(color_count) do
    calculate_bit_size(color_count, 1)
  end

  defp calculate_bit_size(color_count, bit_size) do
    cond do
      Integer.pow(2, bit_size) >= color_count -> bit_size
      true -> calculate_bit_size(color_count, bit_size + 1)
    end
  end

  def empty_picture() do
    <<>>
  end

  def test_picture() do
    <<0::2, 1::2, 2::2, 3::2>>
  end

  def prepend_pixel(picture, color_count, pixel_color_index) do
    bit_size = palette_bit_size(color_count)
    index = <>
    <>
  end

  def get_first_pixel(<<>>, _color_count), do: nil

  def get_first_pixel(picture, color_count) do
    bit_size = palette_bit_size(color_count)
    <> = picture
    first_pixel
  end

  def drop_first_pixel(<<>>, _color_count), do: <<>>

  def drop_first_pixel(picture, color_count) do
    bit_size = palette_bit_size(color_count)
    <<_first_pixel::size(bit_size), rest::bitstring>> = picture
    rest
  end

  def concat_pictures(picture1, picture2) do
    <>
  end
end
# PaintByNumber.palette_bit_size(13)
# PaintByNumber.empty_picture()
# PaintByNumber.test_picture()
<<2::size(4)>>

Dna Encoding

Description Link

Solution

# <> = <<4::4>>
# IO.inspect({a,b,c,d})
# <<:4>> = 65
# int
# <<0b1000::4>>
# 2*(0*2)
# <>
# data = %{?A => [0,0,0,1], ?C => [0,0,1,0], ?G => [0,1,0,0], ?T => [1,0,0,0], 32 => [0,0,0,0]}
# Map.to_list(data)
a =
  [
    {32, [0, 0, 0, 0]},
    {65, [0, 0, 0, 1]},
    {67, [0, 0, 1, 0]},
    {71, [0, 1, 0, 0]},
    {84, [1, 0, 0, 0]}
  ]
  |> En

# str = [0,0,1,0] |> Enum.join()
# <<"0b" <> str::2>>
# [a, b, c| _] = 'AB C'
# c
defmodule DNA do
  @dna_encoding_data [
    {32, [0, 0, 0, 0]},
    {65, [0, 0, 0, 1]},
    {67, [0, 0, 1, 0]},
    {71, [0, 1, 0, 0]},
    {84, [1, 0, 0, 0]}
  ]

  def encode_nucleotide(code_point) do
    binary_code = get(@dna_encoding_data, code_point)
    do_encode_nucleotide(binary_code, 0)
  end

  defp get([], _code_point), do: nil
  defp get([{code_point, value} | tail], code_point), do: value
  defp get([_ | tail], code_point), do: get(tail, code_point)

  defp do_encode_nucleotide([], _exp), do: 0
  defp do_encode_nucleotide([0 | tail], exp), do: do_encode_nucleotide(tail, exp + 1)
  defp do_encode_nucleotide([1 | _tail], exp), do: Integer.pow(2, exp)

  def decode_nucleotide(encoded_code) do
    do_decode_nucleotide(@dna_encoding_data, encoded_code)
  end

  defp do_decode_nucleotide([], _encoded_code), do: nil

  defp do_decode_nucleotide([{k, _value} | tail], encoded_code) do
    case encode_nucleotide(k) do
      ^encoded_code -> k
      _ -> do_decode_nucleotide(tail, encoded_code)
    end
  end

  def encode(dna) do
    do_encode(dna, <<>>)
  end

  defp do_encode([], result), do: result

  defp do_encode([f | t], result),
    do: do_encode(t, <>)

  def decode(dna) do
    do_decode(dna, ~c"")
  end

  defp do_decode(<<>>, result), do: result

  defp do_decode(<>, result),
    do: do_decode(t, result ++ [decode_nucleotide(f)])
end
# DNA.encode_nucleotide(?T)
DNA.decode_nucleotide(0b0001)
# <> = DNA.encode('AC GT')
# DNA.decode(<<132, 2, 1::size(4)>>)

Library Fees

Description Link

Solution

defmodule LibraryFees do
  @days_before_limit 28
  @days_after_limit 29
  @limit_time ~T[12:00:00]

  def datetime_from_string(string) do
    NaiveDateTime.from_iso8601!(string)
  end

  def before_noon?(datetime) do
    noon = @limit_time
    time = NaiveDateTime.to_time(datetime)
    Time.compare(time, noon) == :lt
  end

  def return_date(checkout_datetime) do
    checkout_datetime
    |> NaiveDateTime.to_date()
    |> Date.add(days_to_return(checkout_datetime))
  end

  defp days_to_return(checkout_date) do
    if before_noon?(checkout_date) do
      @days_before_limit
    else
      @days_after_limit
    end
  end

  def days_late(planned_return_date, actual_return_datetime) do
    return_date = NaiveDateTime.to_date(actual_return_datetime)

    case Date.diff(return_date, planned_return_date) do
      days_late when days_late > 0 -> days_late
      _ -> 0
    end
  end

  def monday?(datetime) do
    date = NaiveDateTime.to_date(datetime)
    Date.day_of_week(date) == 1
  end

  def calculate_late_fee(checkout, return, rate) do
    checkout_datetime = datetime_from_string(checkout)
    planned_return_date = return_date(checkout_datetime)
    return_datetime = datetime_from_string(return)
    days_late = days_late(planned_return_date, return_datetime)
    charge = days_late * rate
    maybe_apply_discount(charge, return_datetime)
  end

  defp maybe_apply_discount(charge, datetime) do
    if monday?(datetime), do: trunc(charge * 0.5), else: charge
  end
end
LibraryFees.return_date(~N[2020-02-14 11:59:59Z])