View Source ExRoseTree (ExRoseTree v0.1.3)
A Rose Tree with Functional Zipper in Elixir
what-s-a-rose-tree
What's a Rose Tree?
A Rose Tree, also known as a multi-way or m-way tree by some, is a general-purpose, recursively defined data structure where each position can have an an arbitrary value and an arbitrary number of children. Each child is, itself, another Rose Tree.
ExRoseTree is implemented as a very simple struct defstruct ~w(term children)a
with an equally
simple typespec:
@type t() :: %__MODULE__{
term: term(),
children: [t()]
}
what-s-it-good-for
What's it good for?
Practically speaking, a few good use cases for Rose Trees could be:
- outlines
- file systems
- parsing HTML/XML documents
- abstract syntax trees
- decision trees
- data visualization (org charts, family trees, taxonomies, nested menus)
This implementation also comes with a companion ExRoseTree.Zipper
data structure, and greatly
enhances the usefulness of the standard Rose Tree.
so-what-s-a-zipper
So what's a Zipper?
A Zipper of a given data structure can be thought of as taking the derivative of that data structure. It provides an efficient, context-aware approach to traversing and manipulating the contents of the Rose Tree.
In his foundational paper formalizing the idea, Gerard Huet perhaps describes it best:
The basic idea is simple: the tree is turned inside-out like a returned glove, pointers from the root to the current position being reversed in a path structure. The current location holds both the downward current subtree and the upward path. All navigation and modification primitives operate on the location structure. Going up and down in the structure is analogous to closing and opening a zipper in a piece of clothing, whence the name.
and-what-s-a-zipper-of-a-rose-tree-good-for
And what's a Zipper of a Rose Tree good for?
In practice, ExRoseTree.Zipper
can be used as an effective means of representing everything from a cursor
in a text editor to a selected item in a nested sidebar/dropdown menu in a UI which needs to maintain persistent
focus. Essentially, anything that has an arbitrary hierarchy and would necessitate or benefit from the capability of
being context-aware could be a candidate for a Rose Tree with Zipper.
installation
Installation
If available in Hex, the package can be installed
by adding ex_rose_tree
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_rose_tree, "~> 0.1.0"}
]
end
example-usage
Example Usage
alias ExRoseTree, as: Tree
alias ExRoseTree.Zipper
Tree.new(1)
# %ExRoseTree{term: 1, children: []}
tree = Tree.new(1, [2,3,4,5])
# %ExRoseTree{term: 1, children: [
# %ExRoseTree{term: 2, children: []},
# %ExRoseTree{term: 3, children: []},
# %ExRoseTree{term: 4, children: []},
# %ExRoseTree{term: 5, children: []},
# ]}
zipper = Zipper.new(tree)
# %ExRoseTree.Zipper{
# focus: %ExRoseTree{
# term: 1,
# children: [
# %ExRoseTree{term: 2, children: []},
# %ExRoseTree{term: 3, children: []},
# %ExRoseTree{term: 4, children: []},
# %ExRoseTree{term: 5, children: []}
# ]
# },
# prev: [],
# next: [],
# path: []
# }
zipper = Zipper.last_child(zipper)
# %ExRoseTree.Zipper{
# focus: %ExRoseTree{term: 5, children: []},
# prev: [
# %ExRoseTree{term: 4, children: []},
# %ExRoseTree{term: 3, children: []},
# %ExRoseTree{term: 2, children: []}
# ],
# next: [],
# path: [%ExRoseTree.Zipper.Location{prev: [], term: 1, next: []}]
# }
zipper = Zipper.backward(zipper)
# %ExRoseTree.Zipper{
# focus: %ExRoseTree{term: 4, children: []},
# prev: [
# %ExRoseTree{term: 3, children: []},
# %ExRoseTree{term: 2, children: []}
# ],
# next: [%ExRoseTree{term: 5, children: []}],
# path: [%ExRoseTree.Zipper.Location{prev: [], term: 1, next: []}]
# }
zipper = Zipper.rewind_map(zipper, &Tree.map_term(&1, fn t -> t * 10 end))
# %ExRoseTree.Zipper{
# focus: %ExRoseTree{
# term: 10,
# children: [
# %ExRoseTree{term: 2, children: []},
# %ExRoseTree{term: 3, children: []},
# %ExRoseTree{term: 40, children: []},
# %ExRoseTree{term: 5, children: []},
# ]
# },
# prev: [],
# next: [],
# path: []
# }
Zipper.to_tree(zipper)
# %ExRoseTree{
# term: 10,
# children: [
# %ExRoseTree{term: 2, children: []},
# %ExRoseTree{term: 3, children: []},
# %ExRoseTree{term: 40, children: []},
# %ExRoseTree{term: 5, children: []}
# ]
# }
testing-and-disclaimer
Testing and Disclaimer
While great pains have been taken to provide extensive test coverage--over 800 tests at present, this library is still pre-1.0, so be sure to do your due diligence for your own use case.
To run the test suite:
$ mix deps.get
$ mix test
To run test coverage with excoveralls:
$ mix deps.get
$ mix coveralls
or for HTML output:
$ mix deps.get
$ MIX_ENV=test mix coveralls.html
Link to this section Summary
Types
The foundational, recursive data type of an ExRoseTree
.
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.
Basic Functionality
Returns whether a list of values are all ExRoseTree
s or not. Will return
true
if passed an empty list.
Initializes an empty tree.
Initializes a new tree with the given term
.
Term
Returns the inner term
of an ExRoseTree
.
Applies the given function to the tree term
.
Sets the tree term
to the given term
.
Children
Appends the given child
to the tree
's children.
Returns the children of an ExRoseTree
.
Returns whether or not the current tree
has a child that matches the predicate
.
Inserts a new child
into the tree
's children at the given index
.
Applies the given function to each child tree. The map_fn
should
return a valid ExRoseTree
struct.
Removes the first child of the tree
's children.
Removes the last child of the tree
's children.
Prepends the given child
to the tree
's children.
Removes a child from the tree
's children at the given index
.
Sets the tree
children to the given list of children
.
Special
Given a seed
value and an unfold_fn()
, generates a new rose tree.
Link to this section Types
The foundational, recursive data type of an ExRoseTree
.
term
can by any valid Erlangterm()
children
is a list oft()
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.
Link to this section Guards
Link to this section Basic Functionality
Returns whether a list of values are all ExRoseTree
s or not. Will return
true
if passed an empty list.
examples
Examples
iex> trees = for t <- [5,4,3,2,1], do: ExRoseTree.new(t)
...> ExRoseTree.all_rose_trees?(trees)
true
@spec empty() :: t()
Initializes an empty tree.
examples
Examples
iex> ExRoseTree.empty()
%ExRoseTree{term: nil, children: []}
Initializes a new tree with the given term
.
examples
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: []}
]
}
Link to this section Term
Returns the inner term
of an ExRoseTree
.
examples
Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.get_term(tree)
5
Applies the given function to the tree term
.
examples
Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.map_term(tree, fn x -> x * 2 end)
%ExRoseTree{term: 10, children: []}
Sets the tree term
to the given term
.
examples
Examples
iex> tree = ExRoseTree.new(5)
...> ExRoseTree.set_term(tree, "five")
%ExRoseTree{term: "five", children: []}
Link to this section Children
Appends the given child
to the tree
's children.
examples
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: []}
]
}
Returns the children of an ExRoseTree
.
examples
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: []}
]
Returns whether or not the current tree
has a child that matches the predicate
.
examples
Examples
iex> tree = ExRoseTree.new(5, [4,3,2,1])
...> ExRoseTree.has_child?(tree, &(&1.term == 2))
true
Inserts a new child
into the tree
's children at the given index
.
examples
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: []}
]
}
Applies the given function to each child tree. The map_fn
should
return a valid ExRoseTree
struct.
examples
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: []}
]
}
Removes the first child of the tree
's children.
examples
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: []}
}
Removes the last child of the tree
's children.
examples
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: []}
}
Prepends the given child
to the tree
's children.
examples
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: []}
]
}
Removes a child from the tree
's children at the given index
.
examples
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: []}
}
Sets the tree
children to the given list of children
.
examples
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: []}
]
}
Link to this section Special
Given a seed
value and an unfold_fn()
, generates a new rose tree.
examples
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: []}]
}
]
}
]
}