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

Exercism Elixir Medium

medium.livemd

Exercism Elixir Medium

import ExUnit.Assertions

Mix.install([
  {:benchee_dsl, "~> 0.5"},
  {:benchee_markdown, "~> 0.3"}
])

require Integer
import ExUnit.Assertions

Complex Numbers

https://exercism.org/tracks/elixir/exercises/complex-numbers

Crypto Square

https://exercism.org/tracks/elixir/exercises/crypto-square

Difference of Squares

https://exercism.org/tracks/elixir/exercises/difference-of-squares

Diffie-Hellman

https://exercism.org/tracks/elixir/exercises/diffie-hellman

Dominoes

https://exercism.org/tracks/elixir/exercises/dominoes

Grep

https://exercism.org/tracks/elixir/exercises/grep

Killer Sudoku Helper

https://exercism.org/tracks/elixir/exercises/killer-sudoku-helper

Largest Series Product

https://exercism.org/tracks/elixir/exercises/largest-series-product

Meetup

https://exercism.org/tracks/elixir/exercises/meetup

Parallel Letter Frequency

https://exercism.org/tracks/elixir/exercises/parallel-letter-frequency

Phone Number

https://exercism.org/tracks/elixir/exercises/phone-number

defmodule PhoneNumber do
  @compile {:inline, area_code: 1, canonize: 1, exchange_code: 1}
  @cleanup_regex ~r/[\+\(\)\-\ \.]/

  @doc """
  Remove formatting from a phone number if the given number is valid. Return an error otherwise.
  """
  @spec clean(String.t()) :: {:ok, String.t()} | {:error, String.t()}
  def clean(raw) do
    raw
    |> canonize()
    |> then(&validate(&1, String.length(&1)))
  end

  defp canonize(raw), do: Regex.replace(@cleanup_regex, raw, "")

  defp validate(phone_number) do
    area_code = area_code(phone_number)
    exchange_code = exchange_code(phone_number)

    cond do
      phone_number =~ ~r/\D/ -> {:error, "must contain digits only"}
      area_code == "0" -> {:error, "area code cannot start with zero"}
      area_code == "1" -> {:error, "area code cannot start with one"}
      exchange_code == "0" -> {:error, "exchange code cannot start with zero"}
      exchange_code == "1" -> {:error, "exchange code cannot start with one"}
      true -> {:ok, phone_number}
    end
  end

  defp validate(phone_number, 10), do: validate(phone_number)
  defp validate("1" <> phone_number, 11), do: validate(phone_number)
  defp validate(_phone_number, 11), do: {:error, "11 digits must start with 1"}
  defp validate(_phone_number, _length), do: {:error, "incorrect number of digits"}

  defp area_code(phone_number), do: String.at(phone_number, 0)
  defp exchange_code(phone_number), do: String.at(phone_number, 3)
end

Phone Number: Tests

https://github.com/exercism/elixir/blob/main/exercises/practice/phone-number/test/phone_number_test.exs

assert PhoneNumber.clean("(223) 456-7890") == {:ok, "2234567890"}
assert PhoneNumber.clean("223.456.7890") == {:ok, "2234567890"}
assert PhoneNumber.clean("223 456   7890   ") == {:ok, "2234567890"}
assert PhoneNumber.clean("212555010") == {:error, "incorrect number of digits"}
assert PhoneNumber.clean("22234567890") == {:error, "11 digits must start with 1"}
assert PhoneNumber.clean("12234567890") == {:ok, "2234567890"}
assert PhoneNumber.clean("+1 (223) 456-7890") == {:ok, "2234567890"}
assert PhoneNumber.clean("321234567890") == {:error, "incorrect number of digits"}
assert PhoneNumber.clean("523-abc-7890") == {:error, "must contain digits only"}
assert PhoneNumber.clean("523-@:!-7890") == {:error, "must contain digits only"}
assert PhoneNumber.clean("(023) 456-7890") == {:error, "area code cannot start with zero"}
assert PhoneNumber.clean("(123) 456-7890") == {:error, "area code cannot start with one"}

assert PhoneNumber.clean("(223) 056-7890") ==
         {:error, "exchange code cannot start with zero"}

assert PhoneNumber.clean("(223) 156-7890") ==
         {:error, "exchange code cannot start with one"}

assert PhoneNumber.clean("1 (023) 456-7890") == {:error, "area code cannot start with zero"}
assert PhoneNumber.clean("1 (123) 456-7890") == {:error, "area code cannot start with one"}

assert PhoneNumber.clean("1 (223) 056-7890") ==
         {:error, "exchange code cannot start with zero"}

assert PhoneNumber.clean("1 (223) 156-7890") ==
         {:error, "exchange code cannot start with one"}

:passed

Rational Numbers

https://exercism.org/tracks/elixir/exercises/rational-numbers

Simple Linked List

https://exercism.org/tracks/elixir/exercises/simple-linked-list

Spiral Matrix

https://exercism.org/tracks/elixir/exercises/spiral-matrix

Tournament

https://exercism.org/tracks/elixir/exercises/tournament

Affine Cipher

https://exercism.org/tracks/elixir/exercises/affine-cipher

Bank Account

https://exercism.org/tracks/elixir/exercises/bank-account

Clock

https://exercism.org/tracks/elixir/exercises/clock

Custom Set

https://exercism.org/tracks/elixir/exercises/custom-set

Diamond

https://exercism.org/tracks/elixir/exercises/diamond

Food Chain

https://exercism.org/tracks/elixir/exercises/food-chain

Luhn

https://exercism.org/tracks/elixir/exercises/luhn

Palindrome Products

https://exercism.org/tracks/elixir/exercises/palindrome-products

Pythagorean Triplet

https://exercism.org/tracks/elixir/exercises/pythagorean-triplet

Saddle Points

https://exercism.org/tracks/elixir/exercises/saddle-points

Scale Generator

https://exercism.org/tracks/elixir/exercises/scale-generator

Sieve

https://exercism.org/tracks/elixir/exercises/sieve

Square Root

https://exercism.org/tracks/elixir/exercises/square-root

Transpose

https://exercism.org/tracks/elixir/exercises/transpose

Yacht

https://exercism.org/tracks/elixir/exercises/yacht

Knapsack

https://exercism.org/tracks/elixir/exercises/knapsack

List Ops

https://exercism.org/tracks/elixir/exercises/list-ops

Markdown

https://exercism.org/tracks/elixir/exercises/markdown

OCR Numbers

https://exercism.org/tracks/elixir/exercises/ocr-numbers

Rail Fence Cipher

https://exercism.org/tracks/elixir/exercises/rail-fence-cipher

Robot Simulator

https://exercism.org/tracks/elixir/exercises/robot-simulator

Satellite

https://exercism.org/tracks/elixir/exercises/satellite

State of Tic-Tac-Toe

https://exercism.org/tracks/elixir/exercises/state-of-tic-tac-toe

Variable Length Quantity

https://exercism.org/tracks/elixir/exercises/variable-length-quantity

Alphametics

https://exercism.org/tracks/elixir/exercises/alphametics

Change

https://exercism.org/tracks/elixir/exercises/change

Connect

https://exercism.org/tracks/elixir/exercises/connect

Minesweeper

https://exercism.org/tracks/elixir/exercises/minesweeper

Queen Attack

https://exercism.org/tracks/elixir/exercises/queen-attack

Rectangles

https://exercism.org/tracks/elixir/exercises/rectangles

Two Bucket

https://exercism.org/tracks/elixir/exercises/two-bucket

Word Search

https://exercism.org/tracks/elixir/exercises/word-search

Wordy

https://exercism.org/tracks/elixir/exercises/wordy

Allergies (Binary Reducing)

https://exercism.org/tracks/elixir/exercises/allergies

defmodule Allergies.BinaryReducing do
  @doc """
  List the allergies for which the corresponding flag bit is true.
  """
  @spec list(non_neg_integer) :: [String.t()]
  def list(flags), do: list(<>, [])

  defp list(<<1::size(1), r::size(7)>>, acc), do: list(<<0::size(1), r::size(7)>>, ["cats" | acc])

  defp list(<>, acc),
    do: list(<>, ["pollen" | acc])

  defp list(<>, acc),
    do: list(<>, ["chocolate" | acc])

  defp list(<>, acc),
    do: list(<>, ["tomatoes" | acc])

  defp list(<>, acc),
    do: list(<>, ["strawberries" | acc])

  defp list(<>, acc),
    do: list(<>, ["shellfish" | acc])

  defp list(<>, acc),
    do: list(<>, ["peanuts" | acc])

  defp list(<>, acc), do: list(<>, ["eggs" | acc])

  defp list(<<0>>, acc), do: acc

  @doc """
  Returns whether the corresponding flag bit in 'flags' is set for the item.
  """
  @spec allergic_to?(non_neg_integer, String.t()) :: boolean
  def allergic_to?(flags, item) do
    item in list(flags)
  end
end

Allergies (List Comprehension)

defmodule Allergies.ListComprehension do
  import Bitwise

  @allergies %{
    1 => "eggs",
    2 => "peanuts",
    4 => "shellfish",
    8 => "strawberries",
    16 => "tomatoes",
    32 => "chocolate",
    64 => "pollen",
    128 => "cats"
  }

  @doc """
  List the allergies for which the corresponding flag bit is true.
  """
  @spec list(non_neg_integer) :: [String.t()]
  def list(flags) do
    for {value, item} <- @allergies, flagged?(flags, value), do: item
  end

  defp flagged?(flags, value) do
    (flags &amp;&amp;&amp; value) > 0
  end

  @doc """
  Returns whether the corresponding flag bit in 'flags' is set for the item.
  """
  @spec allergic_to?(non_neg_integer, String.t()) :: boolean
  def allergic_to?(flags, item) do
    item in list(flags)
  end
end

Allergies (Enum.filter_map/3)

defmodule Allergies.EnumFilterMap do
  import Bitwise

  @allergies %{
    1 => "eggs",
    2 => "peanuts",
    4 => "shellfish",
    8 => "strawberries",
    16 => "tomatoes",
    32 => "chocolate",
    64 => "pollen",
    128 => "cats"
  }

  @doc """
  List the allergies for which the corresponding flag bit is true.
  """
  @spec list(non_neg_integer) :: [String.t()]
  def list(flags) do
    Enum.filter_map(@allergies, &amp;flagged?(flags, &amp;1), fn {_, item} -> item end)
  end

  defp flagged?(flags, {value, _}) do
    (flags &amp;&amp;&amp; value) > 0
  end

  @doc """
  Returns whether the corresponding flag bit in 'flags' is set for the item.
  """
  @spec allergic_to?(non_neg_integer, String.t()) :: boolean
  def allergic_to?(flags, item) do
    item in list(flags)
  end
end

Allergies (Enum.zip/2, Enum.reduce/3)

defmodule Allergies.EnumZipReduce do
  @allergies %{
    1 => "eggs",
    2 => "peanuts",
    4 => "shellfish",
    8 => "strawberries",
    16 => "tomatoes",
    32 => "chocolate",
    64 => "pollen",
    128 => "cats"
  }

  @doc """
  List the allergies for which the corresponding flag bit is true.
  """
  @spec list(non_neg_integer) :: [String.t()]
  def list(flags) do
    flags
    |> Integer.digits(2)
    |> Enum.reverse()
    |> Enum.zip(@allergies)
    |> Enum.reduce([], fn
      {1, {_, allergy}}, acc -> [allergy | acc]
      _, acc -> acc
    end)
  end

  @doc """
  Returns whether the corresponding flag bit in 'flags' is set for the item.
  """
  @spec allergic_to?(non_neg_integer, String.t()) :: boolean
  def allergic_to?(flags, item) do
    item in list(flags)
  end
end

Allergies: Tests

https://github.com/exercism/elixir/blob/main/exercises/practice/allergies/test/allergies_test.exs

defmodule AllergiesTest do
  defp assert_is_a_set_containing(list, to_contain) do
    set = Enum.into(list, MapSet.new())

    same_contents =
      to_contain
      |> Enum.into(MapSet.new())
      |> MapSet.equal?(set)

    assert same_contents,
           "Expected a set with: #{inspect(to_contain)} got #{inspect(set |> MapSet.to_list())}"
  end

  def test(module) do
    module.list(0) |> assert_is_a_set_containing([])
    module.list(1) |> assert_is_a_set_containing(~w[eggs])
    module.list(2) |> assert_is_a_set_containing(~w[peanuts])
    module.list(8) |> assert_is_a_set_containing(~w[strawberries])
    module.list(3) |> assert_is_a_set_containing(~w[eggs peanuts])
    module.list(5) |> assert_is_a_set_containing(~w[eggs shellfish])

    module.list(248)
    |> assert_is_a_set_containing(~w[strawberries tomatoes chocolate pollen cats])

    module.list(255)
    |> assert_is_a_set_containing(
      ~w[eggs peanuts shellfish strawberries tomatoes chocolate pollen cats]
    )

    module.list(509)
    |> assert_is_a_set_containing(~w[eggs shellfish strawberries tomatoes chocolate pollen cats])

    module.list(257)
    |> assert_is_a_set_containing(~w[eggs])
  end
end

modules = [
  Allergies.BinaryReducing,
  Allergies.ListComprehension,
  Allergies.EnumFilterMap,
  Allergies.EnumZipReduce
]

for module <- modules do
  AllergiesTest.test(module)

  refute module.allergic_to?(0, "eggs")
  assert module.allergic_to?(1, "eggs")
  assert module.allergic_to?(3, "eggs")
  refute module.allergic_to?(2, "eggs")
  assert module.allergic_to?(255, "eggs")
  refute module.allergic_to?(0, "peanuts")
  assert module.allergic_to?(2, "peanuts")
  assert module.allergic_to?(7, "peanuts")
  refute module.allergic_to?(5, "peanuts")
  assert module.allergic_to?(255, "peanuts")
  refute module.allergic_to?(0, "shellfish")
  assert module.allergic_to?(4, "shellfish")
  assert module.allergic_to?(14, "shellfish")
  refute module.allergic_to?(10, "shellfish")
  assert module.allergic_to?(255, "shellfish")
  refute module.allergic_to?(0, "strawberries")
  assert module.allergic_to?(8, "strawberries")
  assert module.allergic_to?(28, "strawberries")
  refute module.allergic_to?(20, "strawberries")
  assert module.allergic_to?(255, "strawberries")
  refute module.allergic_to?(0, "tomatoes")
  assert module.allergic_to?(16, "tomatoes")
  assert module.allergic_to?(56, "tomatoes")
  refute module.allergic_to?(40, "tomatoes")
  assert module.allergic_to?(255, "tomatoes")
  refute module.allergic_to?(0, "chocolate")
  assert module.allergic_to?(32, "chocolate")
  assert module.allergic_to?(112, "chocolate")
  refute module.allergic_to?(80, "chocolate")
  assert module.allergic_to?(255, "chocolate")
  refute module.allergic_to?(0, "pollen")
  assert module.allergic_to?(64, "pollen")
  assert module.allergic_to?(224, "pollen")
  refute module.allergic_to?(160, "pollen")
  assert module.allergic_to?(255, "pollen")
  refute module.allergic_to?(0, "cats")
  assert module.allergic_to?(128, "cats")
  assert module.allergic_to?(192, "cats")
  refute module.allergic_to?(64, "cats")
  assert module.allergic_to?(255, "cats")
end

:passed

Allergies: Benchmark

{:module, name, _binary, _bindings} =
  defmodule Allergies.Benchmark do
    use BencheeDsl.Benchmark

    config(
      warmup: 1,
      time: 3,
      memory_time: 1,
      reduction_time: 1,
      pre_check: true,
      print: [configuration: false]
    )

    inputs(%{"Small" => 0, "Bigger" => 65999})

    job(binary_reducing(input)) do
      Allergies.BinaryReducing.list(input)
    end

    job(list_comprehension(input)) do
      Allergies.ListComprehension.list(input)
    end

    job(enum_filter_map(input)) do
      Allergies.EnumFilterMap.list(input)
    end

    job(enum_zip_reduce(input)) do
      Allergies.EnumZipReduce.list(input)
    end
  end

BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()