defmodule Bunch.List do
@moduledoc """
A bunch of helper functions for list manipulation.
"""
@doc """
Generates a list using generator function and provided initial accumulator.
Successive list elements are generated by calling `f` with the previous accumulator.
The enumeration finishes when it returns `:halt` or `{:halt, accumulator}`.
## Examples
iex> #{inspect(__MODULE__)}.unfoldr(10, fn 0 -> :halt; n -> {:cont, n-1} end)
Enum.to_list(9..0)
iex> f = fn
...> <<size, content::binary-size(size), rest::binary>> -> {:cont, content, rest}
...> binary -> {:halt, binary}
...> end
iex> #{inspect(__MODULE__)}.unfoldr(<<2, "ab", 3, "cde", 4, "fghi">>, f)
{~w(ab cde fghi), <<>>}
iex> #{inspect(__MODULE__)}.unfoldr(<<2, "ab", 3, "cde", 4, "fg">>, f)
{~w(ab cde), <<4, "fg">>}
"""
@spec unfoldr(acc, (acc -> {:cont, a, acc} | {:cont, acc} | :halt | {:halt, acc})) ::
[a] | {[a], acc}
when acc: any, a: any
def unfoldr(acc, f) do
do_unfoldr(acc, f, [])
end
defp do_unfoldr(acc, f, res_acc) do
case f.(acc) do
{:cont, value, acc} -> do_unfoldr(acc, f, [value | res_acc])
{:cont, acc} -> do_unfoldr(acc, f, [acc | res_acc])
{:halt, acc} -> {Enum.reverse(res_acc), acc}
:halt -> Enum.reverse(res_acc)
end
end
@doc """
The version of `unfoldr/2` that res_accepts `:ok` and `:error` return tuples.
Behaves as `unfoldr/2` as long as `:ok` tuples are returned. Upon `:error`
the processing is stopped and error is returned.
## Examples
iex> f = fn
iex> <<a, b, rest::binary>> ->
iex> sum = a + b
iex> if rem(sum, 2) == 1, do: {:ok, {:cont, sum, rest}}, else: {:error, :even_sum}
iex> acc -> {:ok, {:halt, acc}}
iex> end
iex> #{inspect(__MODULE__)}.try_unfoldr(<<1,2,3,4,5>>, f)
{:ok, {[3, 7], <<5>>}}
iex> #{inspect(__MODULE__)}.try_unfoldr(<<2,4,6,8>>, f)
{:error, :even_sum}
"""
@spec try_unfoldr(
acc,
(acc ->
{:ok, {:cont, a, acc} | {:cont, acc} | :halt | {:halt, acc}} | {:error, reason})
) ::
{:ok, [a] | {[a], acc}} | {:error, reason}
when acc: any, a: any, reason: any
def try_unfoldr(acc, f) do
do_try_unfoldr(acc, f, [])
end
defp do_try_unfoldr(acc, f, res_acc) do
case f.(acc) do
{:ok, {:cont, value, acc}} -> do_try_unfoldr(acc, f, [value | res_acc])
{:ok, {:cont, acc}} -> do_try_unfoldr(acc, f, [acc | res_acc])
{:ok, {:halt, acc}} -> {:ok, {Enum.reverse(res_acc), acc}}
{:ok, :halt} -> {:ok, Enum.reverse(res_acc)}
{:error, reason} -> {:error, reason}
end
end
end