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

Exercism Elixir Easy Part 2

easy-part-2.livemd

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

https://github.com/exercism/elixir/blob/main/exercises/practice/run-length-encoding/test/run_length_encoder_test.exs

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

https://github.com/exercism/elixir/blob/main/exercises/practice/scrabble-score/test/scrabble_test.exs

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(&amp;if(bit_set?(code, @reverse_shift), do: &amp;1, else: Enum.reverse(&amp;1)))
  end

  defp bit_set?(integer, shift_bits), do: (integer >>> shift_bits &amp;&amp;&amp; 0b1) == 0b1
end

Secret Handshake: Tests

https://github.com/exercism/elixir/blob/main/exercises/practice/secret-handshake/test/secret_handshake_test.exs

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(&amp;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(&amp;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(&amp;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, &amp;String.slice(s, &amp;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, &amp;(not fun.(&amp;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], &amp;Integer.is_odd/1) == [1, 3]
  assert module.keep([1, 2, 3, 4, 5], &amp;Integer.is_even/1) == [2, 4]

  words = ~w(apple zebra banana zombies cherimoya zelot)
  assert module.keep(words, &amp;String.starts_with?(&amp;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], &amp;Integer.is_odd/1) == [2]
  assert module.discard([1, 2, 3, 4, 5], &amp;Integer.is_even/1) == [1, 3, 5]

  words = ~w(apple zebra banana zombies cherimoya zelot)
  assert module.discard(words, &amp;String.starts_with?(&amp;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, &amp;is_even/1)
    end

    job(straigth(input)) do
      Strain.Straigth.keep(input, &amp;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, &amp;(rem(n, &amp;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

https://github.com/exercism/elixir/blob/main/exercises/practice/sum-of-multiples/test/sum_of_multiples_test.exs

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(&amp;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(&amp;do_encode/1)
    |> to_string()
    |> chunk()
    |> Enum.join(" ")
  end

  @spec decode(String.t()) :: String.t()
  def decode(cipher) do
    cipher
    |> normalize()
    |> Enum.map(&amp;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", &amp;verse/1)
end

Binary Search

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

Binary Search: Tests

https://github.com/exercism/elixir/blob/main/exercises/practice/binary-search/test/binary_search_test.exs

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(&amp; &amp;1)
    |> Enum.map(&amp;elem(&amp;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(&amp;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

https://github.com/exercism/elixir/blob/main/exercises/practice/isbn-verifier/test/isbn_verifier_test.exs

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(&amp;(&amp;1 == " " or &amp;1 == "-"))
    |> Enum.frequencies()
    |> Map.values()
    |> Enum.all?(&amp;(&amp;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(&amp;String.to_integer/1)
      end)

    columns =
      rows
      |> Enum.zip_with(&amp; &amp;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", &amp;Enum.join(&amp;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