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
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 &&& 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, &flagged?(flags, &1), fn {_, item} -> item end)
end
defp flagged?(flags, {value, _}) do
(flags &&& 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()