defmodule ExRoseTree do
@moduledoc File.read!(Path.expand("README.md"))
|> String.split("<!-- README START -->")
|> Enum.at(1)
|> String.split("<!-- README END -->")
|> List.first()
defstruct ~w(term children)a
@typedoc """
The foundational, recursive data type of an `ExRoseTree`.
* `term` can by any valid Erlang `term()`
* `children` is a list of `t()`
"""
@type t() :: %__MODULE__{
term: term(),
children: [t()]
}
@type predicate() :: (t() -> boolean())
###
### GUARDS
###
@doc section: :guards
defguard rose_tree?(value)
when is_struct(value) and value.__struct__ == __MODULE__ and is_list(value.children)
@doc section: :guards
defguard empty?(value) when rose_tree?(value) and value.term == nil and value.children == []
@doc section: :guards
defguard leaf?(value) when rose_tree?(value) and value.children == []
@doc section: :guards
defguard parent?(value)
when rose_tree?(value) and is_list(value.children) and value.children != []
###
### BASIC
###
@doc """
Initializes an empty tree.
## Examples
iex> ExRoseTree.empty()
%ExRoseTree{term: nil, children: []}
"""
@doc section: :basic
@spec empty() :: t()
def empty(), do: %__MODULE__{term: nil, children: []}
@doc """
Initializes a new tree with the given `term`.
## Examples
iex> ExRoseTree.new("new term")
%ExRoseTree{term: "new term", children: []}
iex> ExRoseTree.new(5, [4, 3, 2, 1])
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
}
iex> children = [
...> %ExRoseTree{term: 4, children: []},
...> 3,
...> %ExRoseTree{term: 2, children: []},
...> %ExRoseTree{term: 1, children: []}
...> ]
...> ExRoseTree.new(5, children)
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
}
"""
@doc section: :basic
@spec new(term(), [t() | term()]) :: t()
def new(term, children \\ [])
def new(term, []), do: %__MODULE__{term: term, children: []}
def new(term, children) when is_list(children) do
new_children =
children
|> Enum.map(fn
child when rose_tree?(child) ->
child
child ->
new(child)
end)
%__MODULE__{
term: term,
children: new_children
}
end
@doc """
Returns whether a list of values are all `ExRoseTree`s or not. Will return
`true` if passed an empty list.
## Examples
iex> trees = for t <- [5,4,3,2,1], do: ExRoseTree.new(t)
...> ExRoseTree.all_rose_trees?(trees)
true
"""
@doc section: :basic
@spec all_rose_trees?([term()]) :: boolean()
def all_rose_trees?(values) when is_list(values) do
Enum.all?(values, &rose_tree?(&1))
end
###
### TERM
###
@doc """
Returns the inner `term` of an `ExRoseTree`.
## Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.get_term(tree)
5
"""
@doc section: :term
@spec get_term(t()) :: term()
def get_term(tree) when rose_tree?(tree), do: tree.term
@doc """
Sets the tree `term` to the given `term`.
## Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.set_term(tree, "five")
%ExRoseTree{term: "five", children: []}
"""
@doc section: :term
@spec set_term(t(), term()) :: t()
def set_term(%__MODULE__{} = tree, term) do
%{tree | term: term}
end
@doc """
Applies the given function to the tree `term`.
## Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.map_term(tree, fn x -> x * 2 end)
%ExRoseTree{term: 10, children: []}
"""
@doc section: :term
@spec map_term(t(), (term() -> term())) :: t()
def map_term(%__MODULE__{term: term} = tree, map_fn)
when is_function(map_fn) do
new_term = map_fn.(term)
%{tree | term: new_term}
end
###
### CHILDREN
###
@doc """
Returns whether or not the current `tree` has a child that matches the `predicate`.
## Examples
iex> tree = ExRoseTree.new(5, [4,3,2,1])
...> ExRoseTree.has_child?(tree, &(&1.term == 2))
true
"""
@doc section: :children
@spec has_child?(t(), (t() -> boolean())) :: boolean()
def has_child?(%__MODULE__{children: children} = _tree, predicate) when is_function(predicate) do
Enum.any?(children, predicate)
end
@doc """
Returns the children of an `ExRoseTree`.
## Examples
iex> tree = ExRoseTree.new(5, [4,3,2,1])
...> ExRoseTree.get_children(tree)
[
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
"""
@doc section: :children
@spec get_children(t()) :: [t()]
def get_children(tree) when rose_tree?(tree), do: tree.children
@doc """
Sets the `tree` children to the given list of `children`.
## Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.set_children(tree, [4, 3, 2, 1])
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
}
"""
@doc section: :children
@spec set_children(t(), [t() | term()]) :: t()
def set_children(%__MODULE__{} = tree, children) when is_list(children) do
new_children =
children
|> Enum.map(fn
%__MODULE__{} = child ->
child
child ->
new(child)
end)
%{tree | children: new_children}
end
@doc """
Applies the given function to each child tree. The `map_fn` should
return a valid `ExRoseTree` struct.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.map_children(tree, fn child ->
...> ExRoseTree.map_term(child, fn x -> x * 2 end)
...> end)
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 8, children: []},
%ExRoseTree{term: 6, children: []},
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 2, children: []}
]
}
"""
@doc section: :children
@spec map_children(t(), (t() -> t())) :: t()
def map_children(%__MODULE__{children: children} = tree, map_fn)
when is_function(map_fn) do
new_children =
children
|> Enum.map(fn child -> map_fn.(child) end)
if all_rose_trees?(new_children) do
%{tree | children: new_children}
else
raise ArgumentError, "map_fn must return a valid ExRoseTree struct"
end
end
@doc """
Prepends the given `child` to the `tree`'s children.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.prepend_child(tree, 0)
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 0, children: []},
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
}
"""
@doc section: :children
@spec prepend_child(t(), t() | term()) :: t()
def prepend_child(%__MODULE__{children: children} = tree, child)
when rose_tree?(child) do
%{tree | children: [child | children]}
end
def prepend_child(%__MODULE__{children: children} = tree, child) do
%{tree | children: [new(child) | children]}
end
@doc """
Removes the first child of the `tree`'s children.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.pop_first_child(tree)
{
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
}, %ExRoseTree{term: 4, children: []}
}
"""
@doc section: :children
@spec pop_first_child(t()) :: {t(), t() | nil}
def pop_first_child(%__MODULE__{children: []} = tree), do: {tree, nil}
def pop_first_child(%__MODULE__{children: [child | children]} = tree) do
{%{tree | children: children}, child}
end
@doc """
Appends the given `child` to the `tree`'s children.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.append_child(tree, 0)
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []},
%ExRoseTree{term: 0, children: []}
]
}
"""
@doc section: :children
@spec append_child(t(), t() | term()) :: t()
def append_child(%__MODULE__{children: children} = tree, child)
when rose_tree?(child) do
%{tree | children: children ++ [child]}
end
def append_child(%__MODULE__{children: children} = tree, child) do
%{tree | children: children ++ [new(child)]}
end
@doc """
Removes the last child of the `tree`'s children.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.pop_last_child(tree)
{
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 2, children: []}
]
}, %ExRoseTree{term: 1, children: []}
}
"""
@doc section: :children
@spec pop_last_child(t()) :: {t(), t() | nil}
def pop_last_child(%__MODULE__{children: []} = tree), do: {tree, nil}
def pop_last_child(%__MODULE__{children: children} = tree) do
{new_children, [popped_child | []]} = Enum.split(children, length(children) - 1)
{%{tree | children: new_children}, popped_child}
end
@doc """
Inserts a new `child` into the `tree`'s children at the given `index`.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.insert_child(tree, 3.5, 2)
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 3.5, children: []},
%ExRoseTree{term: 2, children: []},
%ExRoseTree{term: 1, children: []}
]
}
"""
@doc section: :children
@spec insert_child(t(), t() | term(), integer()) :: t()
def insert_child(%__MODULE__{} = tree, child, index) when rose_tree?(child),
do: do_insert_child(tree, child, index)
def insert_child(%__MODULE__{} = tree, child, index),
do: do_insert_child(tree, new(child), index)
@spec do_insert_child(t(), t() | term(), integer()) :: t()
defp do_insert_child(%__MODULE__{children: children} = tree, child, index)
when rose_tree?(child) and is_integer(index) do
{previous_children, next_children} = Enum.split(children, index)
new_children = previous_children ++ [child | next_children]
%{tree | children: new_children}
end
@doc """
Removes a child from the `tree`'s children at the given `index`.
## Examples
iex> tree = ExRoseTree.new(5, [4, 3, 2, 1])
...> ExRoseTree.remove_child(tree, 2)
{
%ExRoseTree{
term: 5,
children: [
%ExRoseTree{term: 4, children: []},
%ExRoseTree{term: 3, children: []},
%ExRoseTree{term: 1, children: []}
]
},
%ExRoseTree{term: 2, children: []}
}
"""
@doc section: :children
@spec remove_child(t(), integer()) :: {t(), t() | nil}
def remove_child(%__MODULE__{children: []} = tree, _index), do: {tree, nil}
def remove_child(%__MODULE__{children: children} = tree, index)
when is_integer(index) and index < 0 do
if abs(index) > length(children) do
{tree, nil}
else
do_remove_child(tree, index)
end
end
def remove_child(%__MODULE__{} = tree, index) when is_integer(index),
do: do_remove_child(tree, index)
@spec do_remove_child(t(), integer()) :: {t(), t() | nil}
defp do_remove_child(%__MODULE__{children: children} = tree, index) when is_integer(index) do
{new_children, removed_child} =
case Enum.split(children, index) do
{previous, []} ->
{previous, nil}
{previous, [removed | next]} ->
{previous ++ next, removed}
end
{%{tree | children: new_children}, removed_child}
end
@typep unfold_acc() :: %{
current: term(),
todo: [term()],
done: [t()]
}
@typedoc """
A function that takes a seed value and returns a new `ExRoseTree` and a
list of new seeds to use for children. Care must be taken that you
don't create a function that infinitely creates new seeds, in
other words, the function should have a terminating base case.
"""
@type unfold_fn() :: (seed :: term() -> {term(), [seed :: term()]})
@doc """
Given a `seed` value and an `unfold_fn()`, generates a new rose tree.
## Examples
iex> unfolder = fn
...> x when x > 0 -> {Integer.to_string(x), Enum.to_list(0..x-1)}
...> x -> {Integer.to_string(x), []}
...> end
...> ExRoseTree.unfold(3, unfolder)
%ExRoseTree{
term: "3",
children: [
%ExRoseTree{term: "0", children: []},
%ExRoseTree{
term: "1",
children: [%ExRoseTree{term: "0", children: []}]
},
%ExRoseTree{
term: "2",
children: [
%ExRoseTree{term: "0", children: []},
%ExRoseTree{
term: "1",
children: [%ExRoseTree{term: "0", children: []}]
}
]
}
]
}
"""
@doc section: :special
@spec unfold(seed :: term(), unfold_fn()) :: t()
def unfold(seed, unfold_fn) when is_function(unfold_fn) do
{current, next} = unfold_fn.(seed)
%{current: current, todo: next, done: []}
|> do_unfold(_stack = [], unfold_fn)
end
@spec do_unfold(unfold_acc(), [term()], unfold_fn()) :: t()
defp do_unfold(%{todo: []} = acc, [] = _stack, unfold_fn) when is_function(unfold_fn),
do: new(acc.current, Enum.reverse(acc.done))
defp do_unfold(%{todo: []} = acc, [top | rest] = _stack, unfold_fn)
when is_function(unfold_fn) do
tree = new(acc.current, Enum.reverse(acc.done))
%{top | done: [tree | top.done]}
|> do_unfold(rest, unfold_fn)
end
defp do_unfold(%{todo: [next | rest]} = acc, stack, unfold_fn)
when is_list(stack) and is_function(unfold_fn) do
case unfold_fn.(next) do
{current, []} ->
%{acc | todo: rest, done: [new(current) | acc.done]}
|> do_unfold(stack, unfold_fn)
{current, todo} ->
%{current: current, todo: todo, done: []}
|> do_unfold([%{acc | todo: rest} | stack], unfold_fn)
end
end
## Implement Enumerable Protocol
defimpl Enumerable do
alias ExRoseTree
def count(%ExRoseTree{term: nil, children: []}), do: {:ok, 0}
def count(_tree), do: {:error, __MODULE__}
def member?(%ExRoseTree{term: nil, children: []}, _value), do: {:ok, false}
def member?(_tree, _value), do: {:error, __MODULE__}
def slice(%ExRoseTree{} = _tree), do: {:error, __MODULE__}
def reduce(%ExRoseTree{} = tree, acc, fun) do
do_reduce(acc, fun, [tree], [])
end
defp do_reduce({:halt, acc}, _fun, _trees, _remaining_tree_sets),
do: {:halted, acc}
defp do_reduce({:suspend, acc}, fun, trees, remaining_tree_sets),
do: {:suspended, acc, &do_reduce(&1, fun, trees, remaining_tree_sets)}
defp do_reduce({:cont, acc}, _fun, [] = _trees, [] = _remaining_tree_sets),
do: {:done, acc}
defp do_reduce({:cont, acc}, fun, [] = _trees, [h | t] = _remaining_tree_sets),
do: do_reduce({:cont, acc}, fun, [h], t)
defp do_reduce(
{:cont, acc},
fun,
[%ExRoseTree{children: []} = h | t],
remaining_tree_sets
),
do: do_reduce(fun.(h.term, acc), fun, t, remaining_tree_sets)
defp do_reduce(
{:cont, acc},
fun,
[%ExRoseTree{children: children} = h | t],
remaining_tree_sets
),
do: do_reduce(fun.(h.term, acc), fun, children, t ++ remaining_tree_sets)
end
end