defmodule RunLengthEncoder do @digits ~w(0 1 2 3 4 5 6 7 8 9) defguardp is_digit(char) when char in @digits defp encode_reducer(char, []), do: [{char, 1}] defp encode_reducer(char, [{char, count} | rest]), do: [{char, count + 1} | rest] defp encode_reducer(char, rest), do: [{char, 1} | rest] defp encode_mapper({char, 1}), do: char defp encode_mapper({char, count}), do: "#{count}#{char}" defp decode_reducer(digit, []) when is_digit(digit), do: [{nil, digit}] defp decode_reducer(digit, [{nil, count} | rest]) when is_digit(digit), do: [{nil, count <> digit} | rest] defp decode_reducer(char, [{nil, count} | rest]) when not is_digit(char), do: [{char, String.to_integer(count)} | rest] defp decode_reducer(digit, rest) when is_digit(digit), do: [{nil, digit} | rest] defp decode_reducer(char, rest), do: [{char, 1} | rest] defp decode_mapper({char, 1}), do: char defp decode_mapper({char, count}), do: String.duplicate(char, count) @doc """ Generates a string where consecutive elements are represented as a data value and count. "AABBBCCCC" => "2A3B4C" 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. "2A3B4C" => "AABBBCCCC" """ @spec encode(String.t()) :: String.t() def encode(string) do string |> String.graphemes() |> Enum.reduce([], &encode_reducer/2) |> Enum.reverse() |> Enum.map_join(&encode_mapper/1) end @spec decode(String.t()) :: String.t() def decode(string) do string |> String.graphemes() |> Enum.reduce([], &decode_reducer/2) |> Enum.reverse() |> Enum.map_join(&decode_mapper/1) end end