Packages

Elixir implementation of X-Diff Plus

Current section

Files

Jump to
xdiff_plus lib xtree algorithms.ex
Raw

lib/xtree/algorithms.ex

defmodule Xtree.Algorithms do
alias Xtree
@type xtree() :: Xtree.t()
@type tree() :: xtree() | %{children: list(tree)}
@type fn_traverse() ::
(node :: xtree(), accumulator :: any() ->
{:ok, accumulator :: any()} | {:halt, accumulator :: any()})
@doc """
Builds a hash map based on `tMD` of each XTree node
"""
def build_hash_map(tree) do
{:ok, hash_map} =
dft_traverse(tree, %{}, fn %{tMD: tMD}, tMD_map ->
tMD_map =
case Map.get(tMD_map, tMD, nil) do
nil -> Map.put(tMD_map, tMD, 1)
value -> Map.put(tMD_map, tMD, value + 1)
end
{:ok, tMD_map}
end)
hash_map
end
@doc """
Builds 3 maps out of an X-Tree:
- hash_map - A map with key being the node hash and value being the node
- id_map - A map with key being the n_id of node and value being the node
- op_map - A map with key being the n_id of node and value being an operation
"""
def build_tree_maps(tree) do
{:ok, {hash_map, id_map, op_map}} =
dft_traverse(tree, {%{}, %{}, %{}}, fn %{n_id: n_id, tMD: tMD} = node,
{tMD_map, id_map, op_map} ->
tMD_map =
case Map.get(tMD_map, tMD, nil) do
nil -> Map.put(tMD_map, tMD, 1)
value -> Map.put(tMD_map, tMD, value + 1)
end
id_map = Map.put(id_map, n_id, node)
# Put into the OpMap `node.n_id` as key and `{operation, ptrNode}` as value
op_map = Map.put(op_map, n_id, nil)
{:ok, {tMD_map, id_map, op_map}}
end)
{hash_map, id_map, op_map}
end
@doc """
Depth-First pre-order Traverse
"""
@spec dft_traverse(list(tree()) | tree(), accumulator :: any(), fn_visit :: fn_traverse()) ::
{:ok, accumulator :: any()} | {:halt, accumulator :: any()}
def dft_traverse([], acc, _fn_visit) do
{:ok, acc}
end
def dft_traverse([node | nodes], acc, fn_visit) do
case dft_traverse(node, acc, fn_visit) do
{:ok, acc} -> dft_traverse(nodes, acc, fn_visit)
{:halt, acc} -> {:halt, acc}
end
end
def dft_traverse(%{children: children} = node, acc, fn_visit) do
case fn_visit.(node, acc) do
{:halt, acc} ->
{:halt, acc}
{:ok, acc} ->
dft_traverse(children, acc, fn_visit)
acc ->
# Same as {:ok, acc}
dft_traverse(children, acc, fn_visit)
end
end
def dft_traverse(_, acc, _) do
{:ok, acc}
end
@doc """
Breadth-First pre-order Traverse
"""
@spec bft_traverse(list(tree()) | tree(), accumulator :: any(), fn_visit :: fn_traverse()) ::
{:ok, accumulator :: any()} | {:halt, accumulator :: any()}
def bft_traverse([], [], acc, _fn_visit) do
{:ok, acc}
end
def bft_traverse([], children, acc, fn_visit) do
bft_traverse(children, [], acc, fn_visit)
end
def bft_traverse([%{children: node_children} = node | nodes], children, acc, fn_visit) do
case fn_visit.(node, acc) do
{:halt, acc} ->
{:halt, acc}
{:skip, acc} ->
bft_traverse(nodes, children, acc, fn_visit)
{:ok, acc} ->
bft_traverse(nodes, Enum.concat(children, node_children), acc, fn_visit)
acc ->
# Same as {:ok, acc}
bft_traverse(nodes, Enum.concat(children, node_children), acc, fn_visit)
end
end
def bft_traverse(%{children: _} = node, acc, fn_visit) do
bft_traverse([node], [], acc, fn_visit)
end
def bft_traverse(_, acc, _) do
{:ok, acc}
end
def df_post_order_traverse(_, acc, _) do
acc
end
@spec walk(node :: tree() | list(tree()), map_func :: (tree() -> any())) :: any()
def walk([], _fn_walk) do
[]
end
def walk([node | nodes], fn_walk) do
ret = walk(node, fn_walk)
[ret | walk(nodes, fn_walk)]
end
def walk(%{children: _} = node, fn_walk) do
case fn_walk.(node) do
%{children: children} = ret ->
children = walk(children, fn_walk)
Map.put(ret, :children, children)
ret ->
ret
end
end
def walk(ret, _) do
ret
end
end