Current section
Files
Jump to
Current section
Files
lib/convertat.ex
defmodule Convertat do
@moduledoc """
Provides functions for converting **from** and **to** arbitrary bases.
"""
@type integer_base :: 2..36
@type list_base :: []
@doc """
Converts any string of digits or list of digits (where each digit is a string)
to a value in decimal base (base 10), given a starting base.
The starting base can be an integer in the `2..36` range (in which case the
native `String.to_integer/2` function is used) or a list with at least two
elements (digits).
## Examples
iex> "101" |> Convertat.from_base(2)
5
iex> "fe" |> Convertat.from_base(16)
254
iex> "foo" |> Convertat.from_base(["f", "o"])
3
iex> "↑" |> Convertat.from_base(["↓", "↑"])
1
iex> ["foo", "bar"] |> Convertat.from_base(["bar", "foo"])
2
iex> "test" |> Convertat.from_base(["onedigit"])
** (ArgumentError) list bases must have at least two digits
"""
@spec from_base(String.t, integer_base) :: integer
@spec from_base([] | String.t, list_base) :: integer
def from_base(digits, base)
def from_base("", _), do: 0
def from_base([], _), do: 0
def from_base(digits, base) when is_binary(digits) and is_integer(base),
do: String.to_integer(digits, base)
def from_base(_digits, base) when is_list(base) and length(base) < 2,
do: raise(ArgumentError, "list bases must have at least two digits")
def from_base(digits, base) when is_binary(digits) and is_list(base),
do: digits |> String.codepoints |> from_base(base)
def from_base(digits, base) when is_list(digits) and is_list(base) do
numeric_base = Enum.count(base)
digits_map = base
|> Enum.map(&to_string/1)
|> Enum.with_index
|> Enum.into(%{})
Enum.reduce digits, 0, fn(digit, acc) ->
Dict.get(digits_map, digit) + numeric_base * acc
end
end
@doc """
Converts a value in decimal base (`val`, which has to be an integer) to an
arbitrary base. If the `:as_list` option is true, the resulting value in base
`base` will be returned as a list of digits instead of a string of digits.
## Examples
iex> 35 |> Convertat.to_base(36)
"z"
iex> 11 |> Convertat.to_base(["a", "b"])
"babb"
iex> 6 |> Convertat.to_base(["foo", "bar"], as_list: true)
["bar", "bar", "foo"]
iex> 10 |> Convertat.to_base(["↓", "↑"])
"↑↓↑↓"
iex> 42 |> Convertat.to_base(["onedigitbase"])
** (ArgumentError) list bases must have at least two digits
"""
@spec to_base(integer, integer_base | list_base, [as_list: true]) :: [String.t]
@spec to_base(integer, integer_base | list_base, [as_list: false]) :: [String.t]
def to_base(val, base, opts \\ [as_list: false])
def to_base(_val, base, _opts) when is_list(base) and length(base) < 2,
do: raise(ArgumentError, "list bases must have at least two digits")
def to_base(val, base, as_list: as_list?) when is_integer(base) do
result = val |> Integer.to_string(base) |> String.downcase
if as_list?, do: String.codepoints(result), else: result
end
def to_base(0, base, as_list: as_list?) do
if as_list?, do: [zero_digit(base)], else: zero_digit(base)
end
def to_base(val, base, opts) do
result = do_to_base(val, base, Enum.count(base)) |> Enum.reverse
if opts[:as_list], do: result, else: Enum.join(result)
end
@spec do_to_base(integer, list_base, non_neg_integer) :: [String.t]
defp do_to_base(val, _base, _numeric_base) when val == 0, do: []
defp do_to_base(val, base, numeric_base) do
digit = Enum.at(base, rem(val, numeric_base))
[digit|do_to_base(div(val, numeric_base), base, numeric_base)]
end
@compile {:inline, zero_digit: 1}
@spec zero_digit(list_base) :: String.t
defp zero_digit(base), do: base |> Enum.at(0) |> to_string
end