DTree
A DTree is a generalization of trees that devide space into regular
partitions in D dimensions, such as
(2D) and
Octrees (3D).
DTrees
The regularity of DTrees makes especially suitable for
spatial information that is also regular in space such as points in a lattice,
because any node in the tree is associated with a specific part of
space, and thus unique associated with a point in the lattice.
Components and Defintions
DTrees consists of at least one node, the root node.
Each node is of either one of two classes: a leaf-node or a
branch-node. A leaf-node contains data, but has no children. A
branch-node has children, but contains no data. Every node knows it own depth
in the tree. Every node except the root node knows its parent and its own index
in the parent, the index being a number between 0 and N. The root
node has no parent, and consequently no index.
Note that given a node's depth, index in the parent and the parent itself, it
is possible to derive its corresponding partition of space. In fact, each node
has a unique identifier that indicates its position in the tree based on this
information. These identifiers can be used to quickly retrieve nodes from the
tree.
Interface
A tree is the holder of the root node. It supports most methods of a node,
forwarding them to the root node. Exceptions are the methods to obtain the
index or parent of the root node because these don't exist by definition.
template <typename T, std::size_t D>
class DTree;
template <typename T, std::size_t D>
class DNode;
Asking the root node for its parent or index will throw an exception:
class NoParentException : public std::exception;
Asking a branch-node for its value will throw an exception:
class BranchNodeException : public std::exception;
Asking a leaf-node for a child node will throw an exception:
class LeafNodeException : public std::exception;
Reading a DTree from invalid input:
class InvalidDTreeXML : public std::exception;
DTree
typedef DNode |
The type of the nodes of this tree. |
typedef index_t |
The type of indices in a branch-node. |
static const std::size_t N |
The number of children of a branch-node. |
DTree(const T value) |
Constructor with initial value for the root node. The root node is
a leaf-node. |
DNode operator[](const index_t i)
throw(LeafNodeException) |
Obtain a child node of the root node. The root node must be
a branch-node. |
const DNode operator[](const index_t i) const
throw(LeafNodeException) |
Obtain a read-only child node of the root node. The root node must
be a branch-node. |
DNode root() |
Obtain the root node. |
operator DNode() |
Implicitly convert the tree to the root node. |
operator const DNode() const |
Implicitly convert the tree to the root node, read-only. |
T& operator()() throw(BranchNodeException) |
Explicitly convert the root node to a reference to the value it
holds. |
const T& operator()() const throw(BranchNodeException) |
Explicitly convert the root node to a read-only reference to the
value it holds. |
bool isLeaf() const |
Returns true if the root node is a leaf-node. |
operator const T&() const throw(BranchNodeException) |
Implicitly convert the root node into a read-only reference to its
value. The root node must be a leaf-node. |
template <typename U>
void collapse(const U value) throw(LeafNodeException) |
Turn the root node from a branch-node into a leaf-node with the
given value, discarding all children. |
void expand() throw(BranchNodeException) |
Expand the root node from a leaf-node into a branch-node. The value
of all children will be the current value of the leaf-node. |
template <typename U>
void expand(const U value) throw(BranchNodeException) |
Expand the root node from a leaf-node into a branch-node. The value
of all children will be the given value. |
template <typename It>
void expand(It first, It last) throw(BranchNodeException) |
Expand the root node from a leaf-node into a branch-node. The value
of all children will be read from the first elements of the range
first to last. |
std::size_t depth() const |
Return the depth of the tree at the root node, which is by
definition zero. |
bool operator==(const DTree &that) const |
Test if two DTrees refer to the same data - not if they
actually contain the same data. |
bool operator!=(const DTree &that) const |
Negation of operator==. |
DTree<T, D> clone() const |
Return DTree containg a copy of this tree. |
std::size_t id() const |
Return a unique identifier associated with the root node.
By definition, that is zero. |
const DNode retrieve(std::size_t id) const throw (LeafNodeException) |
Return the DNode associated with the unique identifier
id. Providing an invalid identifier might cause an exception
to be thrown. |
DNode retrieve(std::size_t id) throw (LeafNodeException) |
Return the DNode associated with the unique identifier
id. Providing an invalid identifier might cause an exception
to be thrown. |
DTree::DNode
typedef index_t |
The type of indices in a branch-node. |
static const std::size_t N |
The number of children of a branch-node. |
T& operator()() throw(BranchNodeException) |
Explicitly convert a node to a reference to the value it
holds. |
const T& operator()() const throw(BranchNodeException) |
Explicitly convert a node to a read-only reference to the value it
holds. |
DNode operator[](const index_t i)
throw(LeafNodeException) |
Obtain a child node of the node. The node must be a
branch-node. |
const DNode operator[](const index_t i) const
throw(LeafNodeException) |
Obtain a read-only child node of the node. The node must
be a branch-node. |
bool isLeaf() const |
Returns true if the node is a leaf-node. |
template <typename U>
void collapse(const U value) throw(LeafNodeException) |
Turn the node from a branch-node into a leaf-node with the
given value, discarding all children. |
void expand() throw(BranchNodeException) |
Expand a leaf-node into a branch-node. The value of all children
will be the current value of the leaf-node. |
template <typename U>
void expand(const U value) throw(BranchNodeException) |
Expand a leaf-node into a branch-node. The value of all children
will be the given value. |
template <typename It>
void expand(It first, It last) throw(BranchNodeException) |
Expand a leaf-node into a branch-node. The value of all children
will be read from the first elements of the range first
to last. |
bool operator==(const DTree &that) const |
Test if to DNodes are equal DNodes frmo the same tree - not if they
actually contain the same data. |
bool operator!=(const DTree &that) const |
Negation of operator==. |
index_t index() const throw (NoParentException) |
Return the index of this node in its parent node. |
std::size_t depth() const |
Return the depth of the tree at this node. |
DNode parent() throw (NoParentException) |
Return the parent node. The root node has no parent and
requesting it will throw an exception. |
const DNode parent() const throw (NoParentException) |
Return the parent node, read-only. The root node has no parent and
requesting it will throw an exception. |
DTree<T, D> clone() const |
Return DTree containg a copy of part of the tree using the copy of
this node as its root. |
std::size_t id() const |
Return a unique identifier associated with this node. |
std::vector<index_t> index_trail() const |
Return a `trail' of indices to take from the root node to
the current node. |
Comparison
To support interaction with datatypes that require comparison, such as std::map,
a less-than-comparable operator is defined on DNodes in a DTree. It compares the nodes by
their id(), which guaratees that the values are unique if the nodes are part of the same tree.
template <typename T, std::size_t D>
bool operator<(const DTree<T, D>::DNode &lhs, const DTree<T, D>::DNode &rhs);
|
Returns true if the id() of lhs is smaller than that of rhs. |
I / O
There are output operators for both entire DTrees and DNodes. An input operator
only exists for entire DTrees, as an input operator for DNodes could lead to
inconsistencies.
template <typename T, std::size_t D>
std::istream& operator>>(std::istream& i_stream,
DTree<T, D> &tree)
throw(cvmlcpp::InvalidDTreeXML) |
Write a DTree to o_stream in formatted XML-like syntax. |
template <typename T, std::size_t D>
std::ostream& operator<<(std::ostream& o_stream,
const DNode<T, D> &node) |
Write a DNode, potentially with sub-nodes, to o_stream in
formatted XML-like syntax. |
template <typename T, std::size_t D>
std::istream& operator>>(std::istream& i_stream,
DTree<T, D> &tree)
throw(cvmlcpp::InvalidDTreeXML) |
Read a DTree in XML format from i_stream. |
Examples
For an example of a DTree holding items from a class-hierarchy, see
Holder.
A test-program
#include <cvmlcpp/volume/DTree>
int main()
{
typedef cvmlcpp::DTree<int, 3> OctTreeInt;
// Initialize the tree with a value
OctTreeInt tree(7);
assert(tree.root()() == 7);
// The tree implicitly converts to its root node
assert(tree.root() == tree);
// Assign new value
tree() = 1; // implicitly assigned to the root node
tree.root()() = 5; // explicitly assigned to the root node
assert(tree.root()() == 5);
// Expanding a node without giving a value copies the current value
// to all new children.
tree.root().expand();
for (unsigned i = 0; i < OctTreeInt::N; ++i)
assert(tree[i]() == 5);
// Change one leaf node of the root and expand it to a branch node.
// The children of that node now all contain "3", the other children of
// the root node still have their original value
tree[0]() = 3;
tree[0].expand();
for (OctTreeInt::index_t i = 1; i < OctTreeInt::N; ++i)
assert(tree[i]() == 5);
for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
assert(tree[0][i]() == 3);
// Test if we can find a node by walking its trail of indices from
// the root node.
for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
{
const std::vector<OctTreeInt::index_t> trail = tree[0][i].index_trail();
OctTreeInt::DNode node = tree.root();
for (OctTreeInt::index_t j = 0; j < trail.size(); ++j)
node = node[ trail[j] ];
assert(node == tree[0][i]);
}
// Collapse the expanded node and assign it a value of 6
tree[0].collapse(6);
assert(tree[0]() == 6);
// Collapse the whole tree and assign it a value of 6
tree.collapse(4);
assert(tree.root()() == 4);
// Collapse nodes with grand-children
tree.expand();
tree[0].expand();
tree[0][0]() = 2;
tree.collapse(-2);
assert(tree.root()() == -2);
// Expand the tree, assign all children different values.
int values [] = {0, 1, 2, 3, 4, 5, 6, 7};
tree.expand(values, values+OctTreeInt::N);
assert(tree[3]() == 3);
// Grab a node in the tree
typedef OctTreeInt::DNode DNode8i;
DNode8i node = tree[3];
// See if all is consistent
assert(node != tree[2]);
assert(node == tree[3]);
assert(node.parent() == node.parent());
assert(node.parent() == tree.root());
assert(node != tree.root());
// Wrong things should throw
try { // Root node has no index!
assert(node.parent().index() >= 0);
assert(false); // previous should throw
}
catch(cvmlcpp::NoParentException &e) {
}
try { // Root node has no parent
DNode8i n = node.parent().parent();
assert(false); // previous should throw
}
catch(cvmlcpp::NoParentException &e) {
}
// Expand the node and fill with N different values
assert(node.isLeaf());
node.expand(values, values+OctTreeInt::N);
assert(!node.isLeaf());
// Verify that the i-th child of the node is the i-th child of the
// 3th child of the root.
for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
{
// This compares the nodes - NOT the values
assert(node[i] == tree[3][i]);
// Although the nodes are different ...
assert(node[i] != tree[i]);
// ... the values should be identical, except the expanded node.
if (i != 3)
{
// A value is obtained with operator()
assert(node[i]() == tree[i]());
}
}
// Copying data
OctTreeInt tree2 = tree.clone(); // Full tree copy
assert(tree2 != tree);
OctTreeInt tree3 = tree[3].clone(); // Partial tree copy;
for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
{
assert(tree [3][i]() == tree2[3][i]()); // all values equal
assert(tree3[i]() == tree2[3][i]()); // all values equal
assert(tree3[i].index() == tree2[3][i].index());
assert(tree3[i].index() == int(i));
assert(tree [3][i].depth() == 2u);
assert(tree2[3][i].depth() == 2u);
assert(tree3[i].depth() == 1u);
}
// Nodes can be identified with their ID. This ID indicates a
// position, it is valid in a copy of the tree.
for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
{
// Can we find ourselves ?
assert( tree[3][i] == tree.retrieve(tree[3][i].id()) );
// Equal in other tree (by value) ?
assert( tree[3][i]() == tree2.retrieve(tree[3][i].id())() );
}
// I/O
std::ofstream out("/tmp/tree.xml");
out << tree;
out.close();
OctTreeInt tree4;
std::ifstream in("/tmp/tree.xml");
in >> tree4;
in.close();
// Equal to other tree (by value) ?
for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
{
if (i != 3)
assert( tree[i]() == tree4.retrieve(tree[i].id())() );
assert( tree[3][i]() == tree4.retrieve(tree[3][i].id())() );
}
return 0;
}
A DTree in XML format.
The XML fragment below encodes a DTree<int, 3>, i.e. an Octree
filled with integers:
<branch>
<leaf>
<index> 0 </index>
<value> 0 </value>
</leaf>
<leaf>
<index> 1 </index>
<value> 1 </value>
</leaf>
<leaf>
<index> 2 </index>
<value> 2 </value>
</leaf>
<branch>
<index> 3 </index>
<leaf>
<index> 0 </index>
<value> 0 </value>
</leaf>
<leaf>
<index> 1 </index>
<value> 1 </value>
</leaf>
<leaf>
<index> 2 </index>
<value> 2 </value>
</leaf>
<leaf>
<index> 3 </index>
<value> 3 </value>
</leaf>
<leaf>
<index> 4 </index>
<value> 4 </value>
</leaf>
<leaf>
<index> 5 </index>
<value> 5 </value>
</leaf>
<leaf>
<index> 6 </index>
<value> 6 </value>
</leaf>
<leaf>
<index> 7 </index>
<value> 7 </value>
</leaf>
</branch>
<leaf>
<index> 4 </index>
<value> 4 </value>
</leaf>
<leaf>
<index> 5 </index>
<value> 5 </value>
</leaf>
<leaf>
<index> 6 </index>
<value> 6 </value>
</leaf>
<leaf>
<index> 7 </index>
<value> 7 </value>
</leaf>
</branch>
Note that the format is not fully XML-compliant:
- the introductory "Doctype" header etc. is missing;
- spaces are required between tags and the values between them;
- a DTD is missing.
|