Packages

Methods for travelsal and modification of Trees using a zipper.

Current section

Files

Jump to
zipper_tree lib zipper_tree.ex
Raw

lib/zipper_tree.ex

defmodule ZipperTree do
@moduledoc """
Provides traversal and modification methods for variadic arity trees. All
methods maintain an active 'cursor' or focus in the tree. The methods will
also technically work for lists too - I guess, if you're into that sorta
thing.
All traversal and insertion methods happen in constant time with exception to
up, which is porportional to how many nodes were junior to the current subtree.
This is an implementation of Gérard Huet's tree with a zipper, essentially a
direct conversion of the published oocaml code to elixir.
"""
defmodule Node do
@moduledoc """
Represents a breadcrumb, or previous location in the tree of the cursor.
left: all previous siblings of the current node.
up: the previous path.
right: all siblings that come after the current tree node/leaf.
"""
defstruct left: [], up: Top, right: []
end
defmodule Loc do
@moduledoc """
Represents a cursor, or focused location in the tree.
loc: the currently focused subtree.
path: the current path back up the tree, read: ZipperTree.Node
"""
defstruct loc: [], path: Top
end
def down(l) when is_list l do
down %Loc{ loc: l }
end
@doc """
descend into the the current subtree.
"""
def down %Loc{loc: t, path: p} do
case t do
[h|trees] ->
%Loc{loc: h, path: %Node{ up: p, right: trees }}
_ ->
{:error, "at leaf"}
end
end
@doc """
move the cursor to the previous subtree
"""
def up %Loc{loc: t, path: p} do
case p do
Top ->
{:error, "at top"}
%Node{left: left, up: up, right: right} ->
%Loc{loc: Enum.reverse(left) ++ [t | right], path: up}
end
end
@doc """
Move to the previous sibling of the current subtree
"""
def left %Loc{loc: t, path: p} do
case p do
Top ->
{:error, "left of top"}
%Node{left: [], up: _, right: _} ->
{:error, "left of first"}
%Node{left: [l|left], up: up, right: right} ->
%Loc{loc: l, path: %Node{left: left, up: up, right: [t|right]}}
end
end
@doc """
Move to the node after the current location.
"""
def right %Loc{loc: t, path: p} do
case p do
Top ->
{:error, "right of top"}
%Node{left: left, up: up, right: [r|right]} ->
%Loc{loc: r, path: %Node{left: [t|left], up: up, right: right}}
_ ->
{:error, "right of last"}
end
end
@doc """
Move to the nth most child of the current subtree.
## Examples
iex> [1,2,[3,4]] |> ZipperTree.nth 3
%ZipperTree.Loc{loc: [3, 4],
path: %ZipperTree.Node{left: [2, 1],
right: [], up: Top}}
"""
def nth loc, n do
case n do
1 ->
down loc
_ when n > 0 ->
right nth(loc, n-1)
_ ->
{:error, "nth expects a postive integer"}
end
end
@doc """
Recursively move to the topmost node in linear time.
"""
def top l do
case l do
%Loc{loc: _, path: Top} ->
l
_ ->
top up l
end
end
@doc """
Change the value of the current node to t.
"""
def change(%Loc{loc: _, path: p}, t), do: %Loc{loc: t, path: p}
@doc """
Insert r after the current node.
"""
def insert_right %Loc{loc: t, path: p}, r do
case p do
Top ->
{:error, "insert of top"}
%Node{right: right} ->
%Loc{loc: t, path: %Node{p | right: [r|right]}}
end
end
@doc """
Insert l before the current node.
"""
def insert_left %Loc{loc: t, path: p}, l do
case p do
Top ->
{:error, "insert of top"}
%Node{left: left} ->
%Loc{loc: t, path: %Node{p | left: [l|left]}}
end
end
@doc """
Insert t1 into the current subtree.
"""
def insert_down %Loc{loc: t, path: p}, t1 do
case t do
_ when is_list t ->
%Loc{loc: t1, path: %Node{up: p, right: t}}
_ ->
{:error, "cannot insert below leaf"}
end
end
end