Current section

Files

Jump to
maildir_commander src mc_tree.erl
Raw

src/mc_tree.erl

-module(mc_tree).
%% this module deal with simple tree. At building phase it is a list of root nodes
%% and each node is a either an Item (leave) or {Item, [Children]} non-leave
%% after finalization, it is a tuple of {Root_list, Children_map, Parent_map}
%% where Root_list is a list of root Items, Children_map is a map of Item to
%% a list of its Children Items, and Parent_map is a map of Item to its Parent item.
-include_lib("kernel/include/logger.hrl").
-export([append/3, finalize/1, flatmap/2, traverse/2, traverse/3, single/1,
root_list/1, any/3, children/2, parent/2, collapse/1, graft/3,
first/1, last/1, next/2, prev/2]).
-type t() :: {list(), map(), map()}.
%% append an Item to the tree at level N
-spec append(term(), integer(), list()) -> list().
append(Item, 0, Tree) -> [Item | Tree];
append(Item, _N, []) -> [Item];
append(Item, N, [{Pid, Children} | Tail]) ->
[{Pid, append(Item, N-1, Children)} | Tail];
append(Item, _N, [Pid | Tail]) -> [{Pid, [Item]} | Tail].
%% finalize a Tree
-spec finalize(list()) -> t().
finalize(Tree) ->
{ nodes_to_items(Tree),
nodes_to_children_map(Tree),
nodes_to_parent_map(undefined, Tree) }.
%% return a degenerated tree with one node
-spec single(term()) -> t().
single(Node) -> { [Node], #{}, #{} }.
%% return a list that apply Fun(Each, Level) to each item
-spec flatmap(function(), t()) -> list().
flatmap(Fun, {Root_list, Children_map, _Parent_map}) ->
flatmap(Fun, 0, Root_list, Children_map).
%% do action on every node in the tree. The action should return ok
%% when successful. return count of successful actions
-spec traverse(function(), t()) -> integer().
traverse(Action, {Root_list, Children_map, _Parent_map}) ->
traverse(Action, 0, Root_list, Children_map).
%% do action on every node that are decendent of list. The action should return ok
%% when successful. return count of successful actions
-spec traverse(function(), list(), t()) -> integer().
traverse(Action, List, {_Root_list, Children_map, _Parent_map}) ->
traverse(Action, 0, List, Children_map).
%% return the root list
-spec root_list(t()) -> list().
root_list({Root_list, _Children_map, _Parent_map}) -> Root_list.
%% get the children of an Item
-spec children(undefined | term(), t()) -> list().
children(undefined, {Root_list, _Children_map, _Parent_map}) -> Root_list;
children(Item, {_Root_list, Children_map, _Parent_map}) ->
maps:get(Item, Children_map, []).
%% get the parent of an Item
-spec parent(term(), t()) -> undefined | term().
parent(Item, {_Root_list, _Children_map, Parent_map}) ->
maps:get(Item, Parent_map, undefined).
%% to cut a Item from the tree and connect to another Item as a Child.
%% First, The Item and its sub tree will be disconnected.
%% The second Item can be undefined, and the Item will be connected to root
-spec graft(term(), undefined | term(), t()) -> list().
graft(Item, undefined, {Root_list, Children_map, Parent_map}) ->
case maps:get(Item, Parent_map, undefined) of
undefined -> {Root_list, Children_map, Parent_map};
Old_parent ->
{ Root_list ++ [Item],
maps:put( Old_parent,
lists:delete(Item, maps:get(Old_parent, Children_map)),
Children_map ),
maps:remove(Item, Parent_map) }
end;
graft(Item, Parent, {Root_list, Children_map, Parent_map}) ->
case maps:get(Item, Parent_map, undefined) of
Parent -> {Root_list, Children_map, Parent_map};
undefined ->
{ lists:delete(Item, Root_list),
maps:put(Parent, maps:get(Parent, Children_map, []) ++ [Item],
Children_map),
maps:put(Item, Parent, Parent_map) };
Old_parent ->
{ Root_list,
maps:merge(Children_map,
#{ Old_parent =>
lists:delete(Item, maps:get(Old_parent, Children_map)),
Parent =>
maps:get(Parent, Children_map, []) ++ [Item] }),
maps:put(Item, Parent, Parent_map) }
end.
%% given an Item, return if any of itself and it's decendants satisfy a prdicate
%% if Item is undefined, run it on all Root_list
-spec any(fun(), undefined | term(), t()) -> boolean().
any(Pred, undefined, {Root_list, Children_map, _Parent_map}) ->
any_in_list(Pred, Root_list, Children_map);
any(Pred, Item, {_Root_list, Children_map, _Parent_map}) ->
case Pred(Item) of
true -> true;
false ->
any_in_list(Pred, maps:get(Item, Children_map, []), Children_map)
end.
%% given a Tree, return the first of the root list
-spec first(t()) -> undefined | term().
first({[], _, _}) -> undefined;
first({[Head | _Tail], _, _}) -> Head.
%% given a Tree, return the last of the tree in tree order
-spec last(t()) -> undefined | term ().
last({[], _, _}) -> undefined;
last({Root_list, Children_map, _}) ->
tail_of(lists:last(Root_list), Children_map).
%% given an Item and a Tree, return the next Item in tree order
%% the tree order is defined as:
%% if the Item has children, it is the first of its children
%% if the Item has sibling, it is the next of its sibling
%% if there is no sibling, go up until there is
-spec next(term(), t()) -> undefined | term().
next(Item, {Root_list, Children_map, Parent_map}) ->
case maps:get(Item, Children_map, []) of
[Head | _] -> Head;
_ -> next_sibling(Item, Root_list, Children_map, Parent_map)
end.
%% given an Item and a Tree, return the previous Item in tree order
%% the tree order is defined as:
%% if the Item has sibling, it is the tail of previous sibling
%% if there is no sibling, return the parent
-spec prev(term(), t()) -> undefined | term().
prev(Item, {Root_list, Children_map, Parent_map}) ->
case maps:get(Item, Parent_map, undefined) of
undefined ->
prev_sibling(Item, undefined, Root_list, Children_map);
Parent ->
prev_sibling(Item, Parent, maps:get(Parent, Children_map, []),
Children_map)
end.
%% collapse a tree so it is shallower and fatter. For example:
%% A -> B -> C -> D
%% becomes
%% A -> B
%% |> C
%% \> D
%% Assume the parent of A has > 1 fanouts
-spec collapse(t()) -> t().
collapse({[Head], Children_map, _Parent_map}) ->
{New_rl, New_cm} = collapse_node_from(Head, [], Children_map),
{New_rl, New_cm, parent_map(New_rl, New_cm)};
collapse({Root_list, Children_map, _Parent_map}) ->
New_cm = lists:foldl(fun collapse_node/2, Children_map, Root_list),
{Root_list, New_cm, parent_map(Root_list, New_cm)}.
%% private functions
flatmap(_Fun, _Level, [], _Children_map) -> [];
flatmap(Fun, Level, [Item | Tail], Children_map) ->
case maps:get(Item, Children_map, []) of
[] -> [Fun(Item, Level) | flatmap(Fun, Level, Tail, Children_map)];
Children ->
[Fun(Item, Level) | flatmap(Fun, Level + 1, Children, Children_map)] ++
flatmap(Fun, Level, Tail, Children_map)
end.
traverse(_Action, Count, [], _Children_map) -> Count;
traverse(Action, Count, [Item | Tail], Children_map) ->
Count1 = traverse(Action, Count,
maps:get(Item, Children_map, []),
Children_map),
Count2 = traverse(Action, Count1, Tail, Children_map),
case Action(Item) of
ok -> Count2 + 1;
_ -> Count2
end.
nodes_to_items(List) -> nodes_to_items([], List).
nodes_to_items(List, []) -> List;
nodes_to_items(List, [{Item, _Children} | Tail]) -> nodes_to_items([Item | List], Tail);
nodes_to_items(List, [Item | Tail]) -> nodes_to_items([Item | List], Tail).
nodes_to_children_map([]) -> #{};
nodes_to_children_map([{Item, Children} | Tail]) ->
maps:merge( maps:put( Item, nodes_to_items(Children),
nodes_to_children_map(Children) ),
nodes_to_children_map(Tail) );
nodes_to_children_map([_Item | Tail]) ->
nodes_to_children_map(Tail).
nodes_to_parent_map(_Parent, []) -> #{};
nodes_to_parent_map(undefined, [{Item, Children} | Tail]) ->
maps:merge( nodes_to_parent_map(Item, Children),
nodes_to_parent_map(undefined, Tail) );
nodes_to_parent_map(undefined, [_Item | Tail]) ->
nodes_to_parent_map(undefined, Tail);
nodes_to_parent_map(Parent, [{Item, Children} | Tail]) ->
maps:merge( maps:put( Item, Parent,
nodes_to_parent_map(Item, Children) ),
nodes_to_parent_map(Parent, Tail) );
nodes_to_parent_map(Parent, [Item | Tail]) ->
maps:put(Item, Parent, nodes_to_parent_map(Parent, Tail)).
any_in_list(_Pred, [], _Children_map) -> false;
any_in_list(Pred, [Head | Tail], Children_map) ->
case Pred(Head) of
true -> true;
false ->
case any_in_list(Pred, maps:get(Head, Children_map, []), Children_map) of
true -> true;
false -> any_in_list(Pred, Tail, Children_map)
end
end.
next_sibling(Item, Root_List, Children_map, Parent_map) ->
case maps:get(Item, Parent_map, undefined) of
undefined -> next_in_list(Item, Root_List);
Parent ->
case next_in_list(Item, maps:get(Parent, Children_map, [])) of
undefined -> next_sibling(Parent, Root_List, Children_map, Parent_map);
Sibling -> Sibling
end
end.
next_in_list(_Item, []) -> undefined;
next_in_list(Item, [Item]) -> undefined;
next_in_list(Item, [Item, Next | _]) -> Next;
next_in_list(Item, [_Head | Tail]) -> next_in_list(Item, Tail).
prev_sibling(Item, Parent, List, Children_map) ->
case prev_in_list(Item, List) of
undefined -> Parent;
Sibling -> tail_of(Sibling, Children_map)
end.
prev_in_list(_Item, []) -> undefined;
prev_in_list(Item, [Item | _]) -> undefined;
prev_in_list(Item, [Head, Item | _]) -> Head;
prev_in_list(Item, [_Head | Tail]) -> prev_in_list(Item, Tail).
tail_of(Item, Children_map) ->
case maps:get(Item, Children_map, []) of
[] -> Item;
List -> tail_of(lists:last(List), Children_map)
end.
parent_map([], _Children_map) -> #{};
parent_map([Head | Tail], Children_map) ->
Children = maps:get(Head, Children_map, []),
Map1 = maps:from_list(lists:map(
fun (Each) -> {Each, Head} end,
Children)),
Map2 = parent_map(Children, Children_map),
Map3 = parent_map(Tail, Children_map),
maps:merge(Map1, maps:merge(Map2, Map3)).
collapse_node_from(Node, List, Children_map) ->
case maps:get(Node, Children_map, []) of
[Head] ->
collapse_node_from(Head, [Node | List], maps:remove(Node, Children_map));
Clist ->
{ lists:reverse([Node | List]),
lists:foldl(fun collapse_node/2, Children_map, Clist) }
end.
collapse_node(Node, Children_map) ->
case maps:get(Node, Children_map, []) of
[Head] ->
{List, New_map} = collapse_node_from(Head, [], Children_map),
maps:put(Node, List, New_map);
List ->
lists:foldl(fun collapse_node/2, Children_map, List)
end.