In GenTree each node is described as an independent elixir process that is spawned by an Agent.
Agents are a simple abstraction around state.
Often in Elixir there is a need to share or store state that must be accessed
from different processes or by the same process at different points in time.
The Agent module provides a basic server implementation that allows state to be
retrieved and updated via a simple API.
Thus a node can be POINTED to with the pid and data can be stored in the process.
def new(data) do
{:ok, node_pid} = Agent.start(fn -> %{
data: nil,
left: nil,
right: nil,
parent: nil,
children: []
}
|> Map.put(:data, data) end)
node_pid
end
That being done, implementing rest of the tree functions are quite straight forward.
Reducer
-
A reducer function usage example
iex> root = GenTree.from_list([1,2,3,nil,4,5,7,nil,nil,8,9]) iex> GenTree.reduce(root, 0, fn node_pid, acc -> acc + GenTree.get_data(node_pid) end) 39 iex> GenTree.reduce(root, [], fn node_pid, acc -> acc ++ [GenTree.get_data(node_pid)] end, [search: :dfs, order: :postorder]) [4, 2, 8, 9, 5, 7, 3, 1] iex> GenTree.dfs(root, :postorder) [4, 2, 8, 9, 5, 7, 3, 1] iex> GenTree.reduce(root, [], fn node_pid, acc -> acc ++ [node_pid] end, [search: :dfs, order: :postorder]) |> ...> Enum.map(fn node_pid -> GenTree.get_data(node_pid) end) [4, 2, 8, 9, 5, 7, 3, 1] iex> GenTree.reduce(root, [], fn node_pid, acc -> acc ++ [node_pid] end, [search: :dfs, order: :postorder]) [#PID<0.209.0>, #PID<0.207.0>, #PID<0.212.0>, #PID<0.213.0>, #PID<0.210.0>, #PID<0.211.0>, #PID<0.208.0>, #PID<0.206.0>]
Traversals
-
BFS
Usage
iex> root = GenTree.from_list([1,2,3,4,5,6]) #PID<0.427.0> iex> GenTree.Traversal.bfs(root) [1, 2, 3, 4, 5, 6]
@spec bfs(pid, any, (any(), any() -> any())) :: any def bfs(root_node_pid, initial_value \\ [], reducer_function \\ fn x, acc -> acc ++ [x |> GenTree.get_data()] end) do q = :queue.new() q = :queue.in(root_node_pid, q) {:ok, agent_pid} = Agent.start(fn -> initial_value end) bfs_h(q, agent_pid, reducer_function) end defp bfs_h(queue, agent_pid, reducer_function) do cond do :queue.is_empty(queue) -> Agent.get(agent_pid, fn traversed_node_pids -> traversed_node_pids end) true -> { {:value, node_pid}, queue } = :queue.out(queue) Agent.update(agent_pid, fn traversed_node_pids -> reducer_function.(node_pid, traversed_node_pids) end) queue = node_pid |> GenTree.get_children() |> Enum.reverse() |> Enum.reduce(queue, fn child_pid, queue -> :queue.in(child_pid, queue) end) bfs_h(queue, agent_pid, reducer_function) end end
-
DFS
Usage
iex> root = GenTree.from_list([1,2,3,4,5,6]) #PID<0.378.0> iex> GenTree.Traversal.dfs(root, :inorder) [4, 2, 5, 1, 6, 3] iex> GenTree.Traversal.dfs(root, :preorder) [1, 2, 4, 5, 3, 6] iex> GenTree.Traversal.dfs(root, :postorder) [4, 5, 2, 6, 3, 1]
Driver function
@spec dfs(pid, :postorder|:inorder|:preorder, any, (any(), any() -> any())) :: any def dfs(root_node_pid, traversal_type, initial_value \\ [], reducer_function \\ fn x, acc -> acc ++ [x |> GenTree.get_data()] end) do stack = [root_node_pid] {:ok, agent_pid} = Agent.start(fn -> initial_value end) visit_map = %{root_node_pid => true} dfs( stack, agent_pid, traversal_type, visit_map, reducer_function) end defp dfs([], agent_pid, _traversal_type, _visit_map, _reducer_function) do Agent.get(agent_pid, fn traversal_data -> traversal_data end) end defp dfs([node_pid| _rest_stack] = stack, agent_pid, traversal_type, visit_map, reducer_function) do left_child = GenTree.get_left(node_pid) right_child = GenTree.get_right(node_pid) {operation, [_head| rest] = stack, visit_map} = push_children(left_child, right_child, stack, visit_map, traversal_type) case operation do :pop -> Agent.update(agent_pid, fn traversal_data -> reducer_function.(node_pid, traversal_data) end) dfs(rest, agent_pid, traversal_type, visit_map, reducer_function) :continue -> dfs(stack, agent_pid, traversal_type, visit_map, reducer_function) end end
Postorder push_children
## postorder traversal defp push_children(left_child, right_child, stack, visit_map, traversal_type) defp push_children(nil, nil, stack, visit_map, :postorder), do: {:pop, stack, visit_map} defp push_children(nil, right_child, stack, visit_map, :postorder) do cond do Map.has_key?(visit_map, right_child) -> {:pop, stack, visit_map} true -> {:continue, [right_child |stack], Map.put(visit_map, right_child, true)} end end defp push_children(left_child, nil, stack, visit_map, :postorder) do cond do Map.has_key?(visit_map, left_child) -> {:pop, stack, visit_map} true -> {:continue, [left_child |stack], Map.put(visit_map, left_child, true)} end end defp push_children(left_child, right_child, stack, visit_map, :postorder) do cond do Map.has_key?(visit_map, left_child) && Map.has_key?(visit_map, right_child) -> {:pop, stack, visit_map} Map.has_key?(visit_map, left_child) && not Map.has_key?(visit_map, right_child) -> {:continue, [right_child |stack], Map.put(visit_map, right_child, true)} not Map.has_key?(visit_map, left_child) && Map.has_key?(visit_map, right_child) -> {:continue, [left_child |stack], Map.put(visit_map, left_child, true)} true -> {:continue, [left_child, right_child |stack], Map.put(visit_map, right_child, true) |> Map.put(left_child, true)} end end
Inorder push_children
## inorder traversal defp push_children(nil, nil, stack, visit_map, :inorder), do: {:pop, stack, visit_map} defp push_children(left_child, nil, stack, visit_map, :inorder) do cond do Map.has_key?(visit_map, left_child) -> {:pop, stack, visit_map} true -> {:continue, [left_child |stack], Map.put(visit_map, left_child, true)} end end defp push_children(nil, right_child, [_head_node| rest_stack] = _stack, visit_map, :inorder) do {:pop, [nil, right_child | rest_stack], visit_map} end defp push_children(left_child, right_child, [head| rest_stack] = stack, visit_map, :inorder) do cond do not Map.has_key?(visit_map, left_child) && not Map.has_key?(visit_map, right_child) -> {:continue, [left_child, head, right_child |rest_stack], Map.put(visit_map, right_child, true) |> Map.put(left_child, true)} true -> {:pop, stack, visit_map} end end
Preorder push_children
## preorder traversal defp push_children(nil, nil, [_head| rest] = _stack, visit_map, :preorder) do {:pop, [nil| rest], visit_map} end defp push_children(nil, right_child, [_head| rest] = _stack, visit_map, :preorder) do {:pop, [nil, right_child | rest], visit_map} end defp push_children(left_child, nil, [_head| rest] = _stack, visit_map, :preorder) do {:pop, [nil, left_child | rest], visit_map} end defp push_children(left_child, right_child, [_head| rest] = _stack, visit_map, :preorder) do {:pop, [nil, left_child, right_child | rest], visit_map} end end