Exercism Elixir Easy Part 2
Mix.install([
{:benchee_dsl, "~> 0.5"},
{:benchee_markdown, "~> 0.3"}
])
require Integer
import ExUnit.Assertions
Run-Length Encoding
https://exercism.org/tracks/elixir/exercises/run-length-encoding
defmodule RunLengthEncoder do
@doc """
Generates a string where consecutive elements are represented as a data value and count.
For this example, assume all input are strings, that are all uppercase letters.
It should also be able to reconstruct the data into its original form.
## Examples
iex> RunLengthEncoder.encode("AABBBCCCC")
"2A3B4C"
iex> RunLengthEncoder.decode("2A3B4C")
"AABBBCCCC"
"""
@spec encode(String.t()) :: String.t()
def encode(string) do
string
|> String.codepoints()
|> Enum.chunk_by(& &1)
|> Enum.map_join(fn
[char] -> char
chars -> "#{length(chars)}#{hd(chars)}"
end)
end
@spec decode(String.t()) :: String.t()
def decode(string) do
~r/(\d*)(.)/
|> Regex.scan(string)
|> Enum.map_join(fn
[_run, "", char] -> char
[_run, num, char] -> String.duplicate(char, String.to_integer(num))
end)
end
end
Run-Length Encoding: Tests
assert RunLengthEncoder.encode("") === ""
assert RunLengthEncoder.encode("XYZ") === "XYZ"
assert RunLengthEncoder.encode("AABBBCCCC") == "2A3B4C"
assert RunLengthEncoder.encode("WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB") ===
"12WB12W3B24WB"
assert RunLengthEncoder.encode(" hsqq qww ") === "2 hs2q q2w2 "
assert RunLengthEncoder.encode("aabbbcccc") === "2a3b4c"
assert RunLengthEncoder.decode("") === ""
assert RunLengthEncoder.decode("XYZ") === "XYZ"
assert RunLengthEncoder.decode("2A3B4C") == "AABBBCCCC"
assert RunLengthEncoder.decode("12WB12W3B24WB") ===
"WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"
assert RunLengthEncoder.decode("2 hs2q q2w2 ") === " hsqq qww "
assert RunLengthEncoder.decode("2a3b4c") === "aabbbcccc"
original = "zzz ZZ zZ"
encoded = RunLengthEncoder.encode(original)
assert RunLengthEncoder.decode(encoded) === original
:passed
Scrabble Score
https://exercism.org/tracks/elixir/exercises/scrabble-score
defmodule Scrabble do
@doc """
Calculate the scrabble score for the word.
## Examples
iex> Scrabble.score("Elixir")
13
"""
@spec score(String.t()) :: non_neg_integer
def score(word) do
for letter <- String.upcase(word) |> to_charlist(), reduce: 0 do
acc ->
acc +
cond do
letter in ~c[AEIOULNRST] -> 1
letter in ~c[DG] -> 2
letter in ~c[BCMP] -> 3
letter in ~c[FHVWY] -> 4
letter in ~c[K] -> 5
letter in ~c[JX] -> 8
letter in ~c[QZ] -> 10
true -> 0
end
end
end
end
Scrabble Score: Tests
assert Scrabble.score("") == 0
assert Scrabble.score(" \t\n") == 0
assert Scrabble.score("A") == 1
assert Scrabble.score("f") == 4
assert Scrabble.score("at") == 2
assert Scrabble.score("zoo") == 12
assert Scrabble.score("street") == 6
assert Scrabble.score("quirky") == 22
assert Scrabble.score("OxyphenButazone") == 41
assert Scrabble.score("pinata") == 8
assert Scrabble.score("abcdefghijklmnopqrstuvwxyz") == 87
:passed
Secret Handshake
https://exercism.org/tracks/elixir/exercises/secret-handshake
defmodule SecretHandshake do
import Bitwise
@reverse_shift 4
@ops ["wink", "double blink", "close your eyes", "jump"]
@doc """
Determine the actions of a secret handshake based on the binary representation of
the given `code`.
If the following bits are set, include the corresponding action in your list
of commands, in order from lowest to highest:
- 1 = wink
- 10 = double blink
- 100 = close your eyes
- 1000 = jump
- 10000 = reverse the order of the operations in the secret handshake
## Examples
iex> SecretHandshake.commands(0b11)
["wink", "double blink"]
"""
@spec commands(code :: integer()) :: list(String.t())
def commands(code) do
@ops
|> Enum.with_index()
|> Enum.reduce([], fn {op, bit_number}, acc ->
if bit_set?(code, bit_number), do: [op | acc], else: acc
end)
|> then(&if(bit_set?(code, @reverse_shift), do: &1, else: Enum.reverse(&1)))
end
defp bit_set?(integer, shift_bits), do: (integer >>> shift_bits &&& 0b1) == 0b1
end
Secret Handshake: Tests
assert SecretHandshake.commands(1) == ["wink"]
assert SecretHandshake.commands(2) == ["double blink"]
assert SecretHandshake.commands(4) == ["close your eyes"]
assert SecretHandshake.commands(8) == ["jump"]
assert SecretHandshake.commands(3) == ["wink", "double blink"]
assert SecretHandshake.commands(19) == ["double blink", "wink"]
assert SecretHandshake.commands(24) == ["jump"]
assert SecretHandshake.commands(16) == []
assert SecretHandshake.commands(15) == ["wink", "double blink", "close your eyes", "jump"]
assert SecretHandshake.commands(31) == ["jump", "close your eyes", "double blink", "wink"]
assert SecretHandshake.commands(0) == []
assert SecretHandshake.commands(32) == []
:passed
Series (String.split/3 and drop first)
https://exercism.org/tracks/elixir/exercises/series
defmodule StringSeries.SplitDrop do
@doc """
Given a string `s` and a positive integer `size`, return all substrings
of that size. If `size` is greater than the length of `s`, or less than 1,
return an empty list.
"""
@spec slices(s :: String.t(), size :: integer) :: list(String.t())
def slices(s, size) when size > 0 do
numbers = String.split(s, "", trim: true)
do_slices(numbers, size, [])
end
def slices(_s, _size), do: []
defp do_slices([], _size, acc), do: Enum.reverse(acc)
defp do_slices([_ | rest] = numbers, size, acc) do
slice = Enum.take(numbers, size) |> Enum.join("")
if String.length(slice) == size do
do_slices(rest, size, [slice | acc])
else
do_slices([], size, acc)
end
end
end
Series (String.graphemes/1 and drop first)
defmodule StringSeries.GraphemesDrop do
@doc """
Given a string `s` and a positive integer `size`, return all substrings
of that size. If `size` is greater than the length of `s`, or less than 1,
return an empty list.
"""
@spec slices(s :: String.t(), size :: integer) :: list(String.t())
def slices(s, size) when size > 0 do
do_slices(String.graphemes(s), size, [])
end
def slices(_s, _size), do: []
defp do_slices([], _size, acc), do: Enum.reverse(acc)
defp do_slices([_ | rest] = numbers, size, acc) do
slice = Enum.take(numbers, size) |> Enum.join("")
if String.length(slice) == size do
do_slices(rest, size, [slice | acc])
else
do_slices([], size, acc)
end
end
end
Series (String.graphemes/1 and Enum.chunk_every/4)
defmodule StringSeries.GraphemesChunk do
@doc """
Given a string `s` and a positive integer `size`, return all substrings
of that size. If `size` is greater than the length of `s`, or less than 1,
return an empty list.
"""
@spec slices(s :: String.t(), size :: integer) :: list(String.t())
def slices(s, size) when size > 0 do
s
|> String.graphemes()
|> Enum.chunk_every(size, 1, :discard)
|> Enum.map(&Enum.join/1)
end
def slices(_s, _size), do: []
end
Series (String.codepoints/1 and Enum.chunk_every/4)
defmodule StringSeries.CodepointsChunk do
@doc """
Given a string `s` and a positive integer `size`, return all substrings
of that size. If `size` is greater than the length of `s`, or less than 1,
return an empty list.
"""
@spec slices(s :: String.t(), size :: integer) :: list(String.t())
def slices(s, size) when size > 0 do
s
|> String.codepoints()
|> Enum.chunk_every(size, 1, :discard)
|> Enum.map(&to_string/1)
end
def slices(_s, _size), do: []
end
Series (to_charlist/1 and Enum.chunk_every/4)
defmodule StringSeries.CharlistChunk do
@doc """
Given a string `s` and a positive integer `size`, return all substrings
of that size. If `size` is greater than the length of `s`, or less than 1,
return an empty list.
"""
@spec slices(s :: String.t(), size :: integer) :: list(String.t())
def slices(s, size) when size > 0 do
s
|> to_charlist()
|> Enum.chunk_every(size, 1, :discard)
|> Enum.map(&to_string/1)
end
def slices(_s, _size), do: []
end
Series (Enum.map/2 and String.slice/3)
defmodule StringSeries.MapSlice do
@doc """
Given a string `s` and a positive integer `size`, return all substrings
of that size. If `size` is greater than the length of `s`, or less than 1,
return an empty list.
"""
@spec slices(s :: String.t(), size :: integer) :: list(String.t())
def slices(s, size) when size > 0 do
upper_boundary = String.length(s) - size
do_slices(s, size, upper_boundary)
end
def slices(_s, _size), do: []
defp do_slices(_s, _size, upper_boundary) when upper_boundary < 0, do: []
defp do_slices(s, size, upper_boundary),
do: Enum.map(0..upper_boundary, &String.slice(s, &1, size))
end
Series: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/series/test/string_series_test.exs
modules = [
StringSeries.SplitDrop,
StringSeries.GraphemesDrop,
StringSeries.GraphemesChunk,
StringSeries.CodepointsChunk,
StringSeries.CharlistChunk,
StringSeries.MapSlice
]
for module <- modules do
assert module.slices("1", 1) == ["1"]
assert module.slices("12", 1) == ["1", "2"]
assert module.slices("01234", 1) == ["0", "1", "2", "3", "4"]
assert module.slices("35", 2) == ["35"]
assert module.slices("9142", 2) == ["91", "14", "42"]
assert module.slices("01234", 2) == ["01", "12", "23", "34"]
assert module.slices("01234", 3) == ["012", "123", "234"]
assert module.slices("01234", 4) == ["0123", "1234"]
assert module.slices("777777", 3) == ["777", "777", "777", "777"]
assert module.slices("918493904243", 5) == [
"91849",
"18493",
"84939",
"49390",
"93904",
"39042",
"90424",
"04243"
]
assert module.slices("01234", 5) == ["01234"]
assert module.slices("José", 1) == ["J", "o", "s", "é"]
assert module.slices("José", 2) == ["Jo", "os", "sé"]
assert module.slices("01234", 6) == []
assert module.slices("01234", -1) == []
assert module.slices("01234", 0) == []
end
:passed
Series: Benchmark
{:module, name, _binary, _bindings} =
defmodule Benchmark.Series do
use BencheeDsl.Benchmark
config(
warmup: 1,
time: 3,
memory_time: 1,
reduction_time: 1,
pre_check: true,
print: [configuration: false]
)
inputs(%{"Small" => {"1234", 3}, "Bigger" => {"12345678901234567890", 3}})
job(split_drop({s, size})) do
StringSeries.SplitDrop.slices(s, size)
end
job(graphemes_drop({s, size})) do
StringSeries.GraphemesDrop.slices(s, size)
end
job(graphemes_chunk({s, size})) do
StringSeries.GraphemesChunk.slices(s, size)
end
job(codepoints_chunk({s, size})) do
StringSeries.CodepointsChunk.slices(s, size)
end
job(charlist_chunk({s, size})) do
StringSeries.CharlistChunk.slices(s, size)
end
job(map_slice({s, size})) do
StringSeries.MapSlice.slices(s, size)
end
end
BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
Space Age
https://exercism.org/tracks/elixir/exercises/space-age
defmodule SpaceAge do
@moduledoc """
Provides a function to convert how old someone would be on other planet.
## Examples
iex> SpaceAge.age_on(:mars, 30)
{:ok, 5.054416463435008e-7}
iex> SpaceAge.age_on(:venus, 30)
{:ok, 1.5452647406473634e-6}
"""
@type planet ::
:mercury
| :venus
| :earth
| :mars
| :jupiter
| :saturn
| :uranus
| :neptune
@seconds_in_earth_year 31_557_600
@planets %{
earth: @seconds_in_earth_year,
mercury: @seconds_in_earth_year * 0.2408467,
venus: @seconds_in_earth_year * 0.61519726,
mars: @seconds_in_earth_year * 1.8808158,
jupiter: @seconds_in_earth_year * 11.862615,
saturn: @seconds_in_earth_year * 29.447498,
uranus: @seconds_in_earth_year * 84.016846,
neptune: @seconds_in_earth_year * 164.79132
}
@doc """
Return the number of years a person that has lived for 'seconds' seconds is
aged on 'planet'.
"""
@spec age_on(planet(), pos_integer()) :: {:ok, float()} | {:error, String.t()}
for {planet, divider} <- @planets do
def age_on(unquote(planet), seconds), do: {:ok, seconds / unquote(divider)}
end
def age_on(_planet, _seconds), do: {:error, "not a planet"}
end
Space Age: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/space-age/test/space_age_test.exs
input = 1_000_000_000
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 31.69, age, 0.005
input = 2_134_835_688
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 67.65, age, 0.005
{:ok, age} = SpaceAge.age_on(:mercury, input)
assert_in_delta 280.88, age, 0.005
input = 189_839_836
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 6.02, age, 0.005
{:ok, age} = SpaceAge.age_on(:venus, input)
assert_in_delta 9.78, age, 0.005
input = 2_129_871_239
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 67.49, age, 0.005
{:ok, age} = SpaceAge.age_on(:mars, input)
assert_in_delta 35.88, age, 0.005
input = 901_876_382
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 28.58, age, 0.005
{:ok, age} = SpaceAge.age_on(:jupiter, input)
assert_in_delta 2.41, age, 0.005
input = 2_000_000_000
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 63.38, age, 0.005
{:ok, age} = SpaceAge.age_on(:saturn, input)
assert_in_delta 2.15, age, 0.005
input = 1_210_123_456
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 38.35, age, 0.005
{:ok, age} = SpaceAge.age_on(:uranus, input)
assert_in_delta 0.46, age, 0.005
input = 1_821_023_456
{:ok, age} = SpaceAge.age_on(:earth, input)
assert_in_delta 57.70, age, 0.005
{:ok, age} = SpaceAge.age_on(:neptune, input)
assert_in_delta 0.35, age, 0.005
input = 680_804_807
assert SpaceAge.age_on(:sun, input) == {:error, "not a planet"}
:passed
Strain (Expressed)
https://exercism.org/tracks/elixir/exercises/strain
defmodule Strain.Expressed do
@doc """
Given a `list` of items and a function `fun`, return the list of items where
`fun` returns true.
## Examples
iex> Strain.Expressed.keep([1, 2], & &1 > 1)
[2]
"""
@spec keep(list :: list(any()), fun :: (any() -> boolean())) :: list(any)
def keep(list, fun), do: do_filter(list, fun, [])
@doc """
Given a `list` of items and a function `fun`, return the list of items where
`fun` returns false.
## Examples
iex> Strain.Expressed.discard([1, 2], & &1 > 1)
[1]
"""
@spec discard(list :: list(any), fun :: (any -> boolean)) :: list(any)
def discard(list, fun), do: do_filter(list, &(not fun.(&1)), [])
defp do_filter([], _fun, acc), do: Enum.reverse(acc)
defp do_filter([item | tail], fun, acc) do
case fun.(item) do
true -> do_filter(tail, fun, [item | acc])
false -> do_filter(tail, fun, acc)
end
end
end
Strain (Straigth)
defmodule Strain.Straigth do
@doc """
Given a `list` of items and a function `fun`, return the list of items where
`fun` returns true.
## Examples
iex> Strain.Straigth.keep([1, 2], & &1 > 1)
[2]
"""
@spec keep(list :: list(any()), fun :: (any() -> boolean())) :: list(any)
def keep([], _fun), do: []
def keep([item | tail], fun) do
case fun.(item) do
true -> [item | keep(tail, fun)]
false -> keep(tail, fun)
end
end
@doc """
Given a `list` of items and a function `fun`, return the list of items where
`fun` returns false.
## Examples
iex> Strain.Straigth.discard([1, 2], & &1 > 1)
[1]
"""
@spec discard(list :: list(any), fun :: (any -> boolean)) :: list(any)
def discard([], _fun), do: []
def discard([item | tail], fun) do
case fun.(item) do
true -> discard(tail, fun)
false -> [item | discard(tail, fun)]
end
end
end
Strain: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/strain/test/strain_test.exs
for module <- [Strain.Expressed, Strain.Straigth] do
assert module.keep([], fn _ -> true end) == []
assert module.keep([1, 2, 3], fn e -> e < 10 end) == [1, 2, 3]
assert module.keep([1, 2, 3], &Integer.is_odd/1) == [1, 3]
assert module.keep([1, 2, 3, 4, 5], &Integer.is_even/1) == [2, 4]
words = ~w(apple zebra banana zombies cherimoya zelot)
assert module.keep(words, &String.starts_with?(&1, "z")) == ~w(zebra zombies zelot)
rows = [
[1, 2, 3],
[5, 5, 5],
[5, 1, 2],
[2, 1, 2],
[1, 5, 2],
[2, 2, 1],
[1, 2, 5]
]
assert module.keep(rows, fn row -> 5 in row end) == [
[5, 5, 5],
[5, 1, 2],
[1, 5, 2],
[1, 2, 5]
]
assert module.discard([], fn _ -> true end) == []
assert module.discard([1, 2, 3], fn e -> e > 10 end) == [1, 2, 3]
assert module.discard([1, 2, 3], &Integer.is_odd/1) == [2]
assert module.discard([1, 2, 3, 4, 5], &Integer.is_even/1) == [1, 3, 5]
words = ~w(apple zebra banana zombies cherimoya zelot)
assert module.discard(words, &String.starts_with?(&1, "z")) == ~w(apple banana cherimoya)
rows = [
[1, 2, 3],
[5, 5, 5],
[5, 1, 2],
[2, 1, 2],
[1, 5, 2],
[2, 2, 1],
[1, 2, 5]
]
assert module.discard(rows, fn row -> 5 in row end) == [[1, 2, 3], [2, 1, 2], [2, 2, 1]]
end
:passed
Strain: Benchmark
{:module, name, _binary, _bindings} =
defmodule Strain.Benchmark do
use BencheeDsl.Benchmark
import Integer, only: [is_even: 1]
config(
warmup: 1,
time: 3,
memory_time: 1,
reduction_time: 1,
pre_check: true,
print: [configuration: false]
)
inputs(%{
"Small" => [1, 2, 3, 4],
"Bigger" => [
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4,
1,
2,
3,
4
]
})
job(expressed(input)) do
Strain.Expressed.keep(input, &is_even/1)
end
job(straigth(input)) do
Strain.Straigth.keep(input, &is_even/1)
end
end
BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
Sublist
https://exercism.org/tracks/elixir/exercises/sublist
defmodule Sublist do
@doc """
Returns whether the first list is a sublist or a superlist of the second list
and if not whether it is equal or unequal to the second list.
## Examples
iex> Sublist.compare([1, 2, 3], [1, 2])
:superlist
iex> Sublist.compare([2, 3], [1, 2, 3, 4])
:sublist
"""
def compare([], []), do: :equal
def compare(a, b) do
case {length(a), length(b)} do
{_, 0} ->
:superlist
{0, _} ->
:sublist
{x, x} ->
if a == b, do: :equal, else: :unequal
{x, y} when x < y ->
sublist?(a, b)
{x, y} when x > y ->
case sublist?(b, a) do
:sublist -> :superlist
res -> res
end
end
end
defp sublist?([x | a_rest] = a, [x | b_rest]) do
case try_sublist(a_rest, b_rest) do
:sublist -> :sublist
:unequal -> sublist?(a, b_rest)
end
end
defp sublist?([_ | _] = a, [_ | b]), do: sublist?(a, b)
defp sublist?(_, _), do: :unequal
defp try_sublist([x | a], [x | b]), do: try_sublist(a, b)
defp try_sublist([], _), do: :sublist
defp try_sublist(_, _), do: :unequal
end
Sublist (Comparator)
defmodule Sublist.Comparator do
@doc """
Returns whether the first list is a sublist or a superlist of the second list
and if not whether it is equal or unequal to the second list.
## Examples
iex> Sublist.compare([1, 2, 3], [1, 2])
:superlist
iex> Sublist.compare([2, 3], [1, 2, 3, 4])
:sublist
"""
def compare([], []), do: :equal
def compare(a, b) do
case {length(a), length(b)} do
{_, 0} ->
:superlist
{0, _} ->
:sublist
{x, x} ->
equal?(a, b)
{x, y} when x < y ->
sublist?(a, b)
{x, y} when x > y ->
case sublist?(b, a) do
:sublist -> :superlist
res -> res
end
end
end
defp equal?([x | a], [x | b]), do: equal?(a, b)
defp equal?([_ | _], [_ | _]), do: :unequal
defp equal?([], []), do: :equal
defp sublist?([x | a_rest] = a, [x | b_rest]) do
case try_sublist(a_rest, b_rest) do
:sublist -> :sublist
:unequal -> sublist?(a, b_rest)
end
end
defp sublist?([_ | _] = a, [_ | b]), do: sublist?(a, b)
defp sublist?(_, _), do: :unequal
defp try_sublist([x | a], [x | b]), do: try_sublist(a, b)
defp try_sublist([], _), do: :sublist
defp try_sublist(_, _), do: :unequal
end
Sublist: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/sublist/test/sublist_test.exs
import ExUnit.Assertions
for module <- [Sublist, Sublist.Comparator] do
assert module.compare([], []) == :equal
assert module.compare([], [1, 2, 3]) == :sublist
assert module.compare([1, 2, 3], []) == :superlist
assert module.compare([1, 2, 3], [1, 2, 3]) == :equal
assert module.compare([1, 2, 3], [2, 3, 4]) == :unequal
l = Enum.to_list(1..1_000_000)
assert module.compare(l, l) == :equal
assert module.compare([1, 2, 5], [0, 1, 2, 3, 1, 2, 5, 6]) == :sublist
assert module.compare([1, 1, 2], [0, 1, 1, 1, 2, 1, 2]) == :sublist
assert module.compare([0, 1, 2], [0, 1, 2, 3, 4, 5]) == :sublist
assert module.compare([2, 3, 4], [0, 1, 2, 3, 4, 5]) == :sublist
assert module.compare([3, 4, 5], [0, 1, 2, 3, 4, 5]) == :sublist
assert module.compare([1, 1, 2], [1, 1, 1, 2]) == :sublist
assert module.compare([3, 4, 5], Enum.to_list(1..1_000_000)) == :sublist
assert module.compare(Enum.to_list(10..1_000_001), Enum.to_list(1..1_000_000)) == :unequal
assert module.compare([0, 1, 2, 3, 4, 5], [0, 1, 2]) == :superlist
assert module.compare([0, 1, 2, 3, 4, 5], [2, 3]) == :superlist
assert module.compare([0, 1, 2, 3, 4, 5], [3, 4, 5]) == :superlist
assert module.compare([1, 1, 1, 2], [1, 1, 2]) == :superlist
assert module.compare(Enum.to_list(1..1_000_000), [3, 4, 5]) == :superlist
assert module.compare([1, 3], [1, 2, 3]) == :unequal
assert module.compare([1, 2, 3], [1, 3]) == :unequal
assert module.compare([1, 2], [1, 22]) == :unequal
assert module.compare([1, 2, 3], [3, 2, 1]) == :unequal
assert module.compare([1, 0, 1], [10, 1]) == :unequal
assert module.compare([1], [1.0, 2]) == :unequal
assert module.compare([1, 2, 1, 2, 3], [1, 2, 3, 1, 2, 1, 2, 3, 2, 1]) == :sublist
assert module.compare([1, 2, 1, 2, 3], [1, 2, 3, 1, 2, 3, 2, 3, 2, 1]) == :unequal
end
:passed
Sublist: Benchmark
{:module, name, _binary, _bindings} =
defmodule Sublist.Benchmark do
use BencheeDsl.Benchmark
config(
warmup: 1,
time: 3,
memory_time: 1,
reduction_time: 1,
pre_check: true,
print: [configuration: false]
)
inputs(%{
"Small" => {[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]},
"Bigger" =>
{[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5],
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]}
})
job(native_comparator({a, b})) do
Sublist.compare(a, b)
end
job(comparator({a, b})) do
Sublist.Comparator.compare(a, b)
end
end
BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
Sum of Multiples (MapSet with no multiplication)
https://exercism.org/tracks/elixir/exercises/sum-of-multiples
defmodule SumOfMultiples.MapSetNoMult do
@doc """
Adds up all numbers from 1 to a given end number that are multiples of the factors provided.
"""
@spec to(non_neg_integer, [non_neg_integer]) :: non_neg_integer
def to(limit, factors) do
factors =
for factor <- factors, factor < limit, factor > 0, reduce: MapSet.new() do
acc -> MapSet.union(acc, MapSet.new(multiplies(limit, factor, factor, [])))
end
Enum.sum(factors)
end
defp multiplies(limit, factor, next_n, acc) when next_n < limit,
do: multiplies(limit, factor, next_n + factor, [next_n | acc])
defp multiplies(_limit, _factor, _next_n, acc), do: acc
end
Sum of Multiples (Kernel.rem/2)
defmodule SumOfMultiples.RemDiv do
@doc """
Adds up all numbers from 1 to a given end number that are multiples of the factors provided.
"""
@spec to(non_neg_integer, [non_neg_integer]) :: non_neg_integer
def to(limit, factors) do
factors = for factor <- factors, factor > 0, do: factor
1..(limit - 1)
|> Enum.filter(fn n -> Enum.any?(factors, &(rem(n, &1) == 0)) end)
|> Enum.sum()
end
end
Sum Of Multiples (List Comprehension with uniq: true)
defmodule SumOfMultiples.ListCompUniq do
@doc """
Adds up all numbers from 1 to a given end number that are multiples of the factors provided.
"""
@spec to(non_neg_integer, [non_neg_integer]) :: non_neg_integer
def to(limit, factors) do
for(x <- factors, x > 0, y <- 1..limit, xy = x * y, xy < limit, uniq: true, do: xy)
|> Enum.sum()
end
end
Sum Of Multiples (List Comprehension with uniq: true, swapped)
defmodule SumOfMultiples.ListCompUniq2 do
@doc """
Adds up all numbers from 1 to a given end number that are multiples of the factors provided.
"""
@spec to(non_neg_integer, [non_neg_integer]) :: non_neg_integer
def to(limit, factors) do
for(x <- factors, x > 0, y <- 1..(limit - 1), rem(y, x) == 0, uniq: true, do: y)
|> Enum.sum()
end
end
Sum of Multiples: Tests
modules = [
SumOfMultiples.MapSetNoMult,
SumOfMultiples.RemDiv,
SumOfMultiples.ListCompUniq,
SumOfMultiples.ListCompUniq2
]
for module <- modules do
assert module.to(1, [3, 5]) == 0
assert module.to(4, [3, 5]) == 3
assert module.to(7, [3]) == 9
assert module.to(10, [3, 5]) == 23
assert module.to(100, [3, 5]) == 2318
assert module.to(1000, [3, 5]) == 233_168
assert module.to(20, [7, 13, 17]) == 51
assert module.to(15, [4, 6]) == 30
assert module.to(150, [5, 6, 8]) == 4419
assert module.to(51, [5, 25]) == 275
assert module.to(10000, [43, 47]) == 2_203_160
assert module.to(100, [1]) == 4950
assert module.to(10000, []) == 0
assert module.to(1, [0]) == 0
assert module.to(4, [3, 0]) == 3
assert module.to(10000, [2, 3, 5, 7, 11]) == 39_614_537
end
:passed
Sum of Multiples: Benchmark
{:module, name, _binary, _bindings} =
defmodule Benchmark.SumOfMultiples do
use BencheeDsl.Benchmark
config(
warmup: 1,
time: 3,
memory_time: 1,
reduction_time: 1,
pre_check: true,
print: [configuration: false]
)
inputs(%{"Small" => {20, [3, 5]}, "Bigger" => {10000, [2, 3, 5, 7, 11]}})
job(map_set_no_mult({limit, factors})) do
SumOfMultiples.MapSetNoMult.to(limit, factors)
end
job(rem_div({limit, factors})) do
SumOfMultiples.RemDiv.to(limit, factors)
end
job(list_comp_uniq({limit, factors})) do
SumOfMultiples.ListCompUniq.to(limit, factors)
end
job(list_comp_uniq2({limit, factors})) do
SumOfMultiples.ListCompUniq2.to(limit, factors)
end
end
BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
Triangle
https://exercism.org/tracks/elixir/exercises/triangle
defmodule Triangle do
@type kind :: :equilateral | :isosceles | :scalene
@doc """
Return the kind of triangle of a triangle with 'a', 'b' and 'c' as lengths.
## Examples
iex> Triangle.kind(10, 10, 10)
{:ok, :equilateral}
"""
@spec kind(number(), number(), number()) :: {:ok, kind()} | {:error, String.t()}
def kind(a, b, c), do: apply(&do_kind/3, Enum.sort([a, b, c]))
defguardp is_positive(a) when a > 0
defguardp is_triangle(a, b, c) when a + b >= c
defp do_kind(a, _, _) when not is_positive(a), do: {:error, "all side lengths must be positive"}
defp do_kind(a, b, c) when not is_triangle(a, b, c),
do: {:error, "side lengths violate triangle inequality"}
defp do_kind(a, a, a), do: {:ok, :equilateral}
defp do_kind(a, a, _), do: {:ok, :isosceles}
defp do_kind(a, _, a), do: {:ok, :isosceles}
defp do_kind(_, a, a), do: {:ok, :isosceles}
defp do_kind(_, _, _), do: {:ok, :scalene}
end
Triangle: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/triangle/test/triangle_test.exs
assert Triangle.kind(2, 2, 2) == {:ok, :equilateral}
assert Triangle.kind(10, 10, 10) == {:ok, :equilateral}
assert Triangle.kind(0.5, 0.5, 0.5) == {:ok, :equilateral}
assert Triangle.kind(3, 4, 4) == {:ok, :isosceles}
assert Triangle.kind(4, 3, 4) == {:ok, :isosceles}
assert Triangle.kind(4, 4, 3) == {:ok, :isosceles}
assert Triangle.kind(10, 10, 2) == {:ok, :isosceles}
assert Triangle.kind(0.5, 0.4, 0.5) == {:ok, :isosceles}
assert Triangle.kind(3, 4, 5) == {:ok, :scalene}
assert Triangle.kind(10, 11, 12) == {:ok, :scalene}
assert Triangle.kind(5, 4, 2) == {:ok, :scalene}
assert Triangle.kind(0.4, 0.6, 0.3) == {:ok, :scalene}
assert Triangle.kind(0, 0, 0) == {:error, "all side lengths must be positive"}
assert Triangle.kind(3, 4, -5) == {:error, "all side lengths must be positive"}
assert Triangle.kind(1, 1, 3) == {:error, "side lengths violate triangle inequality"}
assert Triangle.kind(1, 3, 1) == {:error, "side lengths violate triangle inequality"}
assert Triangle.kind(3, 1, 1) == {:error, "side lengths violate triangle inequality"}
assert Triangle.kind(7, 3, 2) == {:error, "side lengths violate triangle inequality"}
:passed
Atbash Cipher
https://exercism.org/tracks/elixir/exercises/atbash-cipher
defmodule Atbash do
@plain ~C{abcdefghijklmnopqrstuvwxyz}
@cipher ~C{zyxwvutsrqponmlkjihgfedcba}
@doc """
Encode a given plaintext to the corresponding ciphertext
## Examples
iex> Atbash.encode("completely insecure")
"xlnko vgvob rmhvx fiv"
"""
@spec encode(String.t()) :: String.t()
def encode(plaintext) do
plaintext
|> normalize()
|> Enum.map(&do_encode/1)
|> to_string()
|> chunk()
|> Enum.join(" ")
end
@spec decode(String.t()) :: String.t()
def decode(cipher) do
cipher
|> normalize()
|> Enum.map(&do_encode/1)
|> to_string()
end
defp normalize(string) do
Regex.replace(~r{\W}, string, "")
|> String.downcase()
|> to_charlist()
end
defp chunk(input) do
Regex.scan(~r(.{1,5}), input) |> List.flatten()
end
for {plain, cipher} <- Enum.zip(@plain, @cipher) do
defp do_encode(unquote(plain)), do: unquote(cipher)
end
defp do_encode(plain), do: plain
end
Atbash Cipher: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/atbash-cipher/test/atbash_test.exs
assert Atbash.encode("yes") == "bvh"
assert Atbash.encode("no") == "ml"
assert Atbash.encode("OMG") == "lnt"
assert Atbash.encode("O M G") == "lnt"
assert Atbash.encode("mindblowingly") == "nrmwy oldrm tob"
assert Atbash.encode("Testing, 1 2 3, testing.") == "gvhgr mt123 gvhgr mt"
assert Atbash.encode("Truth is fiction.") == "gifgs rhurx grlm"
plaintext = "The quick brown fox jumps over the lazy dog."
cipher = "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"
assert Atbash.encode(plaintext) == cipher
cipher = "vcvix rhn"
plaintext = "exercism"
assert Atbash.decode(cipher) == plaintext
cipher = "zmlyh gzxov rhlug vmzhg vkkrm thglm v"
plaintext = "anobstacleisoftenasteppingstone"
assert Atbash.decode(cipher) == plaintext
cipher = "gvhgr mt123 gvhgr mt"
plaintext = "testing123testing"
assert Atbash.decode(cipher) == plaintext
cipher = "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"
plaintext = "thequickbrownfoxjumpsoverthelazydog"
assert Atbash.decode(cipher) == plaintext
cipher = "vc vix r hn"
plaintext = "exercism"
assert Atbash.decode(cipher) == plaintext
cipher = "zmlyhgzxovrhlugvmzhgvkkrmthglmv"
plaintext = "anobstacleisoftenasteppingstone"
assert Atbash.decode(cipher) == plaintext
:passed
Beer Song
https://exercism.org/tracks/elixir/exercises/beer-song
defmodule BeerSong do
@doc """
Get a single verse of the beer song
"""
@spec verse(number :: non_neg_integer()) :: String.t()
def verse(0) do
"""
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
"""
end
def verse(1) do
"""
1 bottle of beer on the wall, 1 bottle of beer.
Take it down and pass it around, no more bottles of beer on the wall.
"""
end
def verse(number) do
"""
#{number} bottles of beer on the wall, #{number} bottles of beer.
#{bottles_left(number - 1)}
"""
end
defp bottles_left(1), do: "Take one down and pass it around, 1 bottle of beer on the wall."
defp bottles_left(number),
do: "Take one down and pass it around, #{number} bottles of beer on the wall."
@doc """
Get the entire beer song for a given range of numbers of bottles.
"""
@spec lyrics(range :: Range.t()) :: String.t()
def lyrics(range \\ 99..0), do: Enum.map_join(range, "\n", &verse/1)
end
Binary Search
https://exercism.org/tracks/elixir/exercises/binary-search
Binary Search: Tests
assert BinarySearch.search({6}, 6) == {:ok, 0}
assert BinarySearch.search({3}, 3) == {:ok, 0}
assert BinarySearch.search({1, 2, 4, 5, 6}, 4) == {:ok, 2}
assert BinarySearch.search({1, 3, 4, 6, 8, 9, 11}, 6) == {:ok, 3}
assert BinarySearch.search({1, 2, 4, 5, 6}, 1) == {:ok, 0}
assert BinarySearch.search({1, 3, 4, 5, 8, 9, 11}, 1) == {:ok, 0}
assert BinarySearch.search({1, 2, 4, 5, 6}, 6) == {:ok, 4}
assert BinarySearch.search({1, 3, 4, 5, 8, 9, 11}, 11) == {:ok, 6}
tuple = {1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634}
assert BinarySearch.search(tuple, 144) == {:ok, 9}
tuple = {1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377}
assert BinarySearch.search(tuple, 21) == {:ok, 5}
assert BinarySearch.search(tuple, 34) == {:ok, 6}
assert BinarySearch.search({2, 4, 6}, 3) == :not_found
assert BinarySearch.search({1, 3, 4, 6, 8, 9, 11}, 7) == :not_found
assert BinarySearch.search({2, 4, 6}, 1) == :not_found
assert BinarySearch.search({1, 3, 4, 6, 8, 9, 11}, 0) == :not_found
assert BinarySearch.search({2, 4, 6}, 9) == :not_found
assert BinarySearch.search({1, 3, 4, 6, 8, 9, 11}, 13) == :not_found
assert BinarySearch.search({}, 1) == :not_found
assert BinarySearch.search({1, 2}, 0) == :not_found
:passed
Bob
https://exercism.org/tracks/elixir/exercises/bob
defmodule Bob do
@doc """
Bob is a lackadaisical teenager. In conversation, his responses are very limited.
## Examples
iex> Bob.hey("")
"Fine. Be that way!"
iex> Bob.hey("SLOW DOWN!")
"Whoa, chill out!"
"""
@spec hey(String.t()) :: String.t()
def hey(input) do
input = String.trim(input)
cond do
silence?(input) -> "Fine. Be that way!"
not letters?(input) and question?(input) -> "Sure."
shout?(input) and question?(input) -> "Calm down, I know what I'm doing!"
not letters?(input) -> "Whatever."
shout?(input) -> "Whoa, chill out!"
question?(input) -> "Sure."
true -> "Whatever."
end
end
defp silence?(input), do: input == ""
defp shout?(input), do: String.upcase(input) == input
defp question?(input), do: String.ends_with?(input, "?")
defp letters?(input), do: input =~ ~r/\p{L}/
end
Gigasecond
https://exercism.org/tracks/elixir/exercises/gigasecond
defmodule Gigasecond do
@type time :: {{pos_integer, pos_integer, pos_integer}, {pos_integer, pos_integer, pos_integer}}
@doc """
Calculate a date one billion seconds after an input date.
"""
@spec from(time) :: time
def from(time) do
seconds = :calendar.datetime_to_gregorian_seconds(time)
:calendar.gregorian_seconds_to_datetime(seconds + 1_000_000_000)
end
end
Gigasecond: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/gigasecond/test/gigasecond_test.exs
assert Gigasecond.from({{2011, 4, 25}, {0, 0, 0}}) == {{2043, 1, 1}, {1, 46, 40}}
assert Gigasecond.from({{1977, 6, 13}, {0, 0, 0}}) == {{2009, 2, 19}, {1, 46, 40}}
assert Gigasecond.from({{1959, 7, 19}, {0, 0, 0}}) == {{1991, 3, 27}, {1, 46, 40}}
assert Gigasecond.from({{2015, 1, 24}, {22, 0, 0}}) == {{2046, 10, 2}, {23, 46, 40}}
assert Gigasecond.from({{2015, 1, 24}, {23, 59, 59}}) == {{2046, 10, 3}, {1, 46, 39}}
:passed
Grade School
https://exercism.org/tracks/elixir/exercises/grade-school
defmodule School do
@moduledoc """
Simulate students in a school.
Each student is in a grade.
"""
@type school :: any()
@doc """
Create a new, empty school.
"""
@spec new() :: school
def new() do
%{}
end
@doc """
Add a student to a particular grade in school.
"""
@spec add(school, String.t(), integer) :: {:ok | :error, school}
def add(school, name, grade) do
case Map.fetch(school, name) do
{:ok, _} -> {:error, school}
_ -> {:ok, Map.update(school, name, [grade], fn existing -> [grade | existing] end)}
end
end
@doc """
Return the names of the students in a particular grade, sorted alphabetically.
"""
@spec grade(school, integer) :: [String.t()]
def grade(school, grade) do
Map.to_list(school)
|> Enum.reduce([], fn {name, grades}, acc ->
for ^grade <- grades, reduce: acc do
acc -> [name | acc]
end
end)
|> Enum.sort()
end
@doc """
Return the names of all the students in the school sorted by grade and name.
"""
@spec roster(school) :: [String.t()]
def roster(school) do
Map.to_list(school)
|> Enum.reduce([], fn {name, grades}, acc ->
for grade <- grades, reduce: acc do
acc -> [{grade, name} | acc]
end
end)
|> Enum.sort_by(& &1)
|> Enum.map(&elem(&1, 1))
end
end
Grade School: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/grade-school/test/school_test.exs
defmodule School.Utils do
def make_school_with_students(students) do
{results, school} =
Enum.reduce(students, {[], School.new()}, fn {student, grade}, {results, school} ->
{result, school} = School.add(school, student, grade)
{[result | results], school}
end)
{Enum.reverse(results), school}
end
end
assert School.roster(School.new()) == []
{result, school} = School.add(School.new(), "Aimee", 2)
assert result == :ok
assert School.roster(school) == ["Aimee"]
students = [{"Blair", 2}, {"James", 2}, {"Paul", 2}]
{results, school} = School.Utils.make_school_with_students(students)
assert results == [:ok, :ok, :ok]
assert School.roster(school) == ["Blair", "James", "Paul"]
students = [{"Blair", 2}, {"James", 2}, {"James", 2}, {"Paul", 2}]
{results, school} = School.Utils.make_school_with_students(students)
assert results == [:ok, :ok, :error, :ok]
assert School.roster(school) == ["Blair", "James", "Paul"]
students = [{"Chelsea", 3}, {"Logan", 7}]
{results, school} = School.Utils.make_school_with_students(students)
assert results == [:ok, :ok]
assert School.roster(school) == ["Chelsea", "Logan"]
students = [{"Blair", 2}, {"James", 2}, {"James", 3}, {"Paul", 3}]
{results, school} = School.Utils.make_school_with_students(students)
assert results == [:ok, :ok, :error, :ok]
assert School.roster(school) == ["Blair", "James", "Paul"]
students = [{"Jim", 3}, {"Peter", 2}, {"Anna", 1}]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.roster(school) == ["Anna", "Peter", "Jim"]
students = [{"Peter", 2}, {"Zoe", 2}, {"Alex", 2}]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.roster(school) == ["Alex", "Peter", "Zoe"]
students = [
{"Peter", 2},
{"Anna", 1},
{"Barb", 1},
{"Zoe", 2},
{"Alex", 2},
{"Jim", 3},
{"Charlie", 1}
]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.roster(school) == ["Anna", "Barb", "Charlie", "Alex", "Peter", "Zoe", "Jim"]
assert School.grade(School.new(), 1) == []
students = [{"Peter", 2}, {"Zoe", 2}, {"Alex", 2}, {"Jim", 3}]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.grade(school, 1) == []
students = [{"Blair", 2}, {"James", 2}, {"James", 2}, {"Paul", 2}]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.roster(school) == ["Blair", "James", "Paul"]
students = [{"Blair", 2}, {"James", 2}, {"James", 3}, {"Paul", 3}]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.grade(school, 2) == ["Blair", "James"]
assert School.grade(school, 3) == ["Paul"]
students = [{"Franklin", 5}, {"Bradley", 5}, {"Jeff", 1}]
{_results, school} = School.Utils.make_school_with_students(students)
assert School.grade(school, 5) == ["Bradley", "Franklin"]
:passed
Grains
https://exercism.org/tracks/elixir/exercises/grains
defmodule Grains do
@doc """
Calculate two to the power of the input minus one.
"""
@spec square(pos_integer()) :: {:ok, pos_integer()} | {:error, String.t()}
def square(number) when number > 0 and number <= 64 do
{:ok, Bitwise.bsl(1, number - 1)}
end
def square(_), do: {:error, "The requested square must be between 1 and 64 (inclusive)"}
@doc """
Adds square of each number from 1 to 64.
"""
@spec total :: {:ok, pos_integer()}
def total do
{:ok, unquote(Bitwise.bsl(1, 64) - 1)}
end
end
Grains: Tests
https://github.com/exercism/elixir/blob/main/exercises/practice/grains/test/grains_test.exs
assert Grains.square(1) === {:ok, 1}
assert Grains.square(2) === {:ok, 2}
assert Grains.square(3) === {:ok, 4}
assert Grains.square(4) === {:ok, 8}
assert Grains.square(16) === {:ok, 32768}
assert Grains.square(32) === {:ok, 2_147_483_648}
assert Grains.square(64) === {:ok, 9_223_372_036_854_775_808}
assert Grains.total() === {:ok, 18_446_744_073_709_551_615}
assert Grains.square(65) ===
{:error, "The requested square must be between 1 and 64 (inclusive)"}
assert Grains.square(-1) ===
{:error, "The requested square must be between 1 and 64 (inclusive)"}
assert Grains.square(0) ===
{:error, "The requested square must be between 1 and 64 (inclusive)"}
:passed
ISBN Verifier (Enum.zip/2, Enum.map/2)
https://exercism.org/tracks/elixir/exercises/isbn-verifier
defmodule IsbnVerifier.EnumFuncs do
@doc """
Checks if a string is a valid ISBN-10 identifier
## Examples
iex> IsbnVerifier.EnumFuncs.isbn?("3-598-21507-X")
true
iex> IsbnVerifier.EnumFuncs.isbn?("3-598-2K507-0")
false
"""
@spec isbn?(String.t()) :: boolean
def isbn?(isbn) do
if Regex.match?(~r/^(\d-?){9}(\d|X)$/, isbn) do
number =
isbn
|> String.replace("-", "")
|> String.graphemes()
|> Enum.zip(10..1)
|> Enum.map(&to_int/1)
|> Enum.sum()
Integer.mod(number, 11) == 0
else
false
end
end
defp to_int({"X", 1}), do: 10
defp to_int({num, x}), do: String.to_integer(num) * x
end
ISBN Verifier (Regex.scan/2)
defmodule IsbnVerifier.RegexScan do
@regex ~r/^(\d)-?(\d)-?(\d)-?(\d)-?(\d)-?(\d)-?(\d)-?(\d)-?(\d)-?(\d|X)$/
@doc """
Checks if a string is a valid ISBN-10 identifier
## Examples
iex> IsbnVerifier.RegexScan.isbn?("3-598-21507-X")
true
iex> IsbnVerifier.RegexScan.isbn?("3-598-2K507-0")
false
"""
@spec isbn?(String.t()) :: boolean
def isbn?(isbn) do
case Regex.scan(@regex, isbn) do
[[_, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10]] ->
Integer.mod(
to_int(d1) * 10 +
to_int(d2) * 9 +
to_int(d3) * 8 +
to_int(d4) * 7 +
to_int(d5) * 6 +
to_int(d6) * 5 +
to_int(d7) * 4 +
to_int(d8) * 3 +
to_int(d9) * 2 +
to_int(d10),
11
) == 0
_ ->
false
end
end
defp to_int("X"), do: 10
defp to_int(num), do: String.to_integer(num)
end
ISBN Verifier: Tests
assert IsbnVerifier.isbn?("3-598-21508-8")
refute IsbnVerifier.isbn?("3-598-21508-9")
assert IsbnVerifier.isbn?("3-598-21507-X")
refute IsbnVerifier.isbn?("3-598-21507-A")
refute IsbnVerifier.isbn?("4-598-21507-B")
refute IsbnVerifier.isbn?("3-598-P1581-X")
refute IsbnVerifier.isbn?("3-598-2X507-9")
assert IsbnVerifier.isbn?("3598215088")
assert IsbnVerifier.isbn?("359821507X")
refute IsbnVerifier.isbn?("359821507")
refute IsbnVerifier.isbn?("3598215078X")
refute IsbnVerifier.isbn?("00")
refute IsbnVerifier.isbn?("3-598-21507")
refute IsbnVerifier.isbn?("3-598-21507-XA")
refute IsbnVerifier.isbn?("3-598-21515-X")
refute IsbnVerifier.isbn?("134456729")
refute IsbnVerifier.isbn?("3132P34035")
refute IsbnVerifier.isbn?("3598P215088")
refute IsbnVerifier.isbn?("98245726788")
assert IsbnVerifier.isbn?("99921-58-10-7")
assert IsbnVerifier.isbn?("9971-5-0210-0")
assert IsbnVerifier.isbn?("960-425-059-0")
assert IsbnVerifier.isbn?("80-902734-1-6")
assert IsbnVerifier.isbn?("85-359-0277-5")
assert IsbnVerifier.isbn?("1-84356-028-3")
assert IsbnVerifier.isbn?("0-684-84328-5")
assert IsbnVerifier.isbn?("0-8044-2957-X")
assert IsbnVerifier.isbn?("0-85131-041-9")
assert IsbnVerifier.isbn?("93-86954-21-4")
assert IsbnVerifier.isbn?("0-943396-04-2")
assert IsbnVerifier.isbn?("0-9752298-0-X")
:passed
ISBN Verifier: Benchmark
{:module, name, _binary, _bindings} =
defmodule IsbnVerifier.Benchmark do
use BencheeDsl.Benchmark
config(
warmup: 1,
time: 3,
memory_time: 1,
reduction_time: 1,
pre_check: true,
print: [configuration: false]
)
job(enum_funs) do
IsbnVerifier.EnumFuncs.isbn?("3-598-21507-X")
end
job(regex_scan) do
IsbnVerifier.RegexScan.isbn?("3-598-21507-X")
end
end
BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
Isogram (Enum.reject/2, Enum.frequencies/1)
https://exercism.org/tracks/elixir/exercises/isogram
defmodule Isogram.EnumFuncs do
@doc """
Determines if a word or sentence is an isogram.
## Examples
iex> Isogram.EnumFuncs.isogram?("PUBVEXINGFJORD-SCHMALTZY")
true
"""
@spec isogram?(String.t()) :: boolean
def isogram?(""), do: true
def isogram?(sentence) do
sentence
|> String.downcase()
|> String.codepoints()
|> Enum.reject(&(&1 == " " or &1 == "-"))
|> Enum.frequencies()
|> Map.values()
|> Enum.all?(&(&1 == 1))
end
end
Isogram (List Substraction)
defmodule Isogram.ListSubstraction do
@ascii_alphabet Enum.to_list(?a..?z)
@doc """
Determines if a word or sentence is an isogram.
## Examples
iex> Isogram.ListSubstraction.isogram?("PUBVEXINGFJORD-SCHMALTZY")
true
"""
@spec isogram?(String.t()) :: boolean
def isogram?(""), do: true
def isogram?(sentence) do
sentence =
~r/[\ \-]/
|> Regex.replace(sentence, "")
|> String.downcase()
|> to_charlist()
Enum.empty?(sentence -- @ascii_alphabet)
end
end
Isogram: Benchmark
{:module, name, _binary, _bindings} =
defmodule Isogram.Benchmark do
use BencheeDsl.Benchmark
config(
warmup: 1,
time: 3,
memory_time: 1,
reduction_time: 1,
pre_check: true,
print: [configuration: false]
)
job(enum_funcs) do
Isogram.EnumFuncs.isogram?("PUBVEXINGFJORD-SCHMALTZY")
end
job(list_substraction) do
Isogram.ListSubstraction.isogram?("PUBVEXINGFJORD-SCHMALTZY")
end
end
BencheeDsl.Livebook.benchee_config() |> name.run() |> BencheeDsl.Livebook.render()
Leap
https://exercism.org/tracks/elixir/exercises/leap
defmodule Year do
@doc """
Returns whether 'year' is a leap year.
A leap year occurs:
- on every year that is evenly divisible by 4
- except every year that is evenly divisible by 100
- unless the year is also evenly divisible by 400
## Examples
iex> Year.leap_year?(2015)
false
iex> Year.leap_year?(2016)
true
"""
@spec leap_year?(non_neg_integer()) :: boolean()
def leap_year?(year) when rem(year, 400) == 0, do: true
def leap_year?(year) when rem(year, 100) == 0, do: false
def leap_year?(year) when rem(year, 4) == 0, do: true
def leap_year?(_year), do: false
end
Matrix
https://exercism.org/tracks/elixir/exercises/matrix
defmodule Matrix do
@type t :: %__MODULE__{
rows: nil | [[integer(), ...], ...],
columns: nil | [[integer(), ...], ...]
}
defstruct rows: nil, columns: nil
@doc """
Convert an `input` string, with rows separated by newlines and values
separated by single spaces, into a `Matrix` struct.
"""
@spec from_string(input :: String.t()) :: t
def from_string(input) do
rows =
input
|> String.split("\n")
|> Enum.map(fn line ->
line
|> String.split()
|> Enum.map(&String.to_integer/1)
end)
columns =
rows
|> Enum.zip_with(& &1)
%__MODULE__{rows: rows, columns: columns}
end
@doc """
Write the `matrix` out as a string, with rows separated by newlines and
values separated by single spaces.
"""
@spec to_string(matrix :: t) :: String.t()
def to_string(%__MODULE__{rows: nil}), do: ""
def to_string(%__MODULE__{rows: rows}), do: Enum.map_join(rows, "\n", &Enum.join(&1, " "))
@doc """
Given a `matrix`, return its rows as a list of lists of integers.
"""
@spec rows(matrix :: t) :: [[integer, ...], ...]
def rows(%__MODULE__{rows: nil}), do: []
def rows(%__MODULE__{rows: rows}), do: rows
@doc """
Given a `matrix` and `index`, return the row at `index`.
"""
@spec row(matrix :: t, index :: integer) :: [integer, ...]
def row(%__MODULE__{rows: rows}, index) when index > 0 and index <= length(rows),
do: Enum.at(rows, index - 1)
def row(_matrix, index), do: raise("index #{inspect(index)} is out of bounds")
@doc """
Given a `matrix`, return its columns as a list of lists of integers.
"""
@spec columns(matrix :: t) :: [[integer, ...], ...]
def columns(%__MODULE__{columns: nil}), do: []
def columns(%__MODULE__{columns: columns}), do: columns
@doc """
Given a `matrix` and `index`, return the column at `index`.
"""
@spec column(matrix :: t, index :: integer) :: [integer, ...]
def column(%__MODULE__{columns: columns}, index) when index > 0 and index <= length(columns),
do: Enum.at(columns, index - 1)
def column(_matrix, index), do: raise("index #{inspect(index)} is out of bounds")
end
Resistor Color Trio
https://exercism.org/tracks/elixir/exercises/resistor-color-trio
defmodule ResistorColorTrio do
@type color ::
:black | :brown | :red | :orange | :yellow | :green | :blue | :violet | :grey | :white
@spec code(color :: color()) :: non_neg_integer()
defp code(:black), do: 0
defp code(:brown), do: 1
defp code(:red), do: 2
defp code(:orange), do: 3
defp code(:yellow), do: 4
defp code(:green), do: 5
defp code(:blue), do: 6
defp code(:violet), do: 7
defp code(:grey), do: 8
defp code(:white), do: 9
@spec value(colors :: [color()]) :: non_neg_integer()
defp value(colors) when length(colors) >= 3 do
[color1, color2, exp_color | _tail] = colors
(code(color1) * 10 + code(color2)) * max(Integer.pow(10, code(exp_color)), 1)
end
@doc """
Calculate the resistance value in ohm or kiloohm from resistor colors.
## Examples
iex> ResistorColorTrio.label([:yellow, :red, :black])
{42, :ohms}
"""
@spec label(colors :: [color()]) :: {pos_integer(), :ohms | :kiloohms | :megaohms | :gigaohms}
def label(colors) when length(colors) >= 3 do
case value(colors) do
value when value >= 1_000_000_000 -> {div(value, 1_000_000_000), :gigaohms}
value when value >= 1_000_000 -> {div(value, 1_000_000), :megaohms}
value when value >= 1000 -> {div(value, 1000), :kiloohms}
value -> {value, :ohms}
end
end
end