containers/ntree.hpp

#ifndef STLPLUS_NTREE
#define STLPLUS_NTREE
////////////////////////////////////////////////////////////////////////////////

//   Author:    Andy Rushton
//   Copyright: (c) Southampton University 1999-2004
//              (c) Andy Rushton           2004 onwards
//   License:   BSD License, see ../docs/license.html

//   A templated n-ary tree data structure. STL-like but the definition of
//   iterators is really only applicable to one-dimensional structures. I use
//   iterators to access tree nodes, but there is no increment or decrement
//   operators for them. I also define prefix and postfix traversal iterators
//   which do have increment.

////////////////////////////////////////////////////////////////////////////////
#include "containers_fixes.hpp"
#include "exceptions.hpp"
#include "safe_iterator.hpp"
#include <vector>
#include <iterator>

namespace stlplus
{

  ////////////////////////////////////////////////////////////////////////////////
  // Internals

  template<typename T> class ntree_node;
  template<typename T> class ntree;
  template<typename T, typename TRef, typename TPtr> class ntree_iterator;
  template<typename T, typename TRef, typename TPtr> class ntree_prefix_iterator;
  template<typename T, typename TRef, typename TPtr> class ntree_postfix_iterator;

  ////////////////////////////////////////////////////////////////////////////////
  // Iterators

  // Simple iterators which are just used as pointers to tree nodes.
  // this is not really an iterator - it is a reference, i.e. an iterator with no increment
  // unfortunately the STL does not provide for this rather useful concept
  // I have called it an iterator for consistency, but it is really an element reference
  // An uninitialised iterator is null - similarly, if you ask for the root of an empty tree or the parent of
  // the root node then you get a null iterator.

  template<typename T, typename TRef, typename TPtr>
  class ntree_iterator : public safe_iterator<ntree<T>,ntree_node<T> >
  {
  public:
    // local type definitions

    // iterator traits, were inherited from std::iterator but inheriting from that was deprecated in C++17
    // there is no relevant iterator category since this is not incremenable
    typedef void iterator_category;
    typedef T value_type;
    typedef TPtr pointer;
    typedef TRef reference;
    typedef void difference_type;

    // an iterator points to an object whilst a const_iterator points to a const object
    typedef ntree_iterator<T,T&,T*> iterator;
    typedef ntree_iterator<T,const T&,const T*> const_iterator;
    typedef ntree_iterator<T,TRef,TPtr> this_iterator;

    // constructor to create a null iterator - you must assign a valid value to this iterator before using it
    ntree_iterator(void);
    ~ntree_iterator(void);

    // Type conversion methods allow const_iterator and iterator to be converted
    const_iterator constify(void) const;
    iterator deconstify(void) const;

    // tests useful for putting iterators into other STL structures and for testing whether iteration has completed
    bool operator == (const this_iterator& r) const;
    bool operator != (const this_iterator& r) const;
    bool operator < (const this_iterator& r) const;

    // access the node data - a const_iterator gives you a const element, an iterator a non-const element
    // it is illegal to dereference an invalid (i.e. null or end) iterator
    // exceptions: null_dereference,end_dereference
    reference operator*(void) const;
    // exceptions: null_dereference,end_dereference
    pointer operator->(void) const;

    friend class ntree<T>;
    friend class ntree_prefix_iterator<T,TRef,TPtr>;
    friend class ntree_postfix_iterator<T,TRef,TPtr>;

  public:
    // Note: I had to make this public to get round a problem implementing persistence - it should be private
    // you cannot create a valid iterator except by calling an ntree method that returns one
    // constructor used by ntree to create a non-null iterator
    explicit ntree_iterator(ntree_node<T>* node);
    // constructor used by ntree to create an end iterator
    explicit ntree_iterator(const ntree<T>* owner);
    // used to create an alias of an iterator
    explicit ntree_iterator(const safe_iterator<ntree<T>, ntree_node<T> >& iterator);
  };

  // Traversal iterators are like iterators but they have increment operators (++)
  // - prefix_iterator visits the nodes of the tree in prefix order
  // - postfix_iterator visits the nodes of the tree in postfix order.
  // There is no such thing as infix order for an n-ary tree and you cannot
  // traverse backwards with these iterators. These follow the STL convention in
  // that you iterate from a begin to an end - in this case ntree exports
  // prefix_begin()/prefix_end() and postfix_begin()/postfix_end(). You can
  // simplify these iterators to the basic iterator above for functions that
  // require a simple iterator.

  template<typename T, typename TRef, typename TPtr>
  class ntree_prefix_iterator
  {
  public:

    // iterator traits, were inherited from std::iterator but inheriting from that was deprecated in C++17
    // the ntree iterators are unidirectional
    typedef std::forward_iterator_tag iterator_category;
    typedef T value_type;
    typedef TPtr pointer;
    typedef TRef reference;
    // this is not random access, so declare a void difference type, not sure this is supported by everything
    // typedef std::ptrdiff_t  difference_type;
    typedef void difference_type;

    // an iterator points to an object whilst a const_iterator points to a const object
    typedef ntree_prefix_iterator<T,T&,T*>             iterator;
    typedef ntree_prefix_iterator<T,const T&,const T*> const_iterator;
    typedef ntree_prefix_iterator<T,TRef,TPtr>         this_iterator;
    typedef ntree_iterator<T,TRef,TPtr>                simple_iterator;

    // constructor to create a null iterator - you must assign a valid value to this iterator before using it
    ntree_prefix_iterator(void);
    ~ntree_prefix_iterator(void);

    // tests
    // a null iterator is one that has not been initialised with a value yet
    // i.e. you just declared it but didn't assign to it
    bool null(void) const;
    // an end iterator is one that points to the end element of the list of nodes
    // in STL conventions this is one past the last valid element and must not be dereferenced
    bool end(void) const;
    // a valid iterator is one that can be dereferenced
    // i.e. non-null and non-end
    bool valid(void) const;

    // Type conversion methods allow const_iterator and iterator to be converted
    // convert an iterator/const_iterator to a const_iterator
    const_iterator constify(void) const;
    iterator deconstify(void) const;

    // generate a simple iterator from a traversal iterator
    simple_iterator simplify(void) const;

    // tests useful for putting iterators into other STL structures and for testing whether iteration has completed
    bool operator == (const this_iterator& r) const;
    bool operator != (const this_iterator& r) const;
    bool operator < (const this_iterator& r) const;

    // increment/decrement operators used to step through the set of all nodes in a graph
    // it is only legal to increment a valid iterator
    // pre-increment
    // exceptions: null_dereference,end_dereference
    this_iterator& operator ++ (void);
    // post-increment
    // exceptions: null_dereference,end_dereference
    this_iterator operator ++ (int);

    // access the node data - a const_iterator gives you a const element, an iterator a non-const element
    // it is illegal to dereference an invalid (i.e. null or end) iterator
    // exceptions: null_dereference,end_dereference
    reference operator*(void) const;
    // exceptions: null_dereference,end_dereference
    pointer operator->(void) const;

    friend class ntree<T>;
    friend class ntree_iterator<T,TRef,TPtr>;

  private:
    simple_iterator m_iterator;

    explicit ntree_prefix_iterator(const simple_iterator& i);
    const simple_iterator& get_iterator(void) const;
    simple_iterator& get_iterator(void);
  };

  ////////////////////////////////////////////////////////////////////////////////

  template<typename T, typename TRef, typename TPtr>
  class ntree_postfix_iterator
  {
  public:

    // iterator traits, were inherited from std::iterator but inheriting from that was deprecated in C++17
    // the ntree iterators are unidirectional
    typedef std::forward_iterator_tag iterator_category;
    typedef T value_type;
    typedef TPtr pointer;
    typedef TRef reference;
    // this is not random access, so declare a void difference type, not sure this is supported by everything
    // typedef std::ptrdiff_t  difference_type;
    typedef void difference_type;

    typedef ntree_postfix_iterator<T,T&,T*>             iterator;
    typedef ntree_postfix_iterator<T,const T&,const T*> const_iterator;
    typedef ntree_postfix_iterator<T,TRef,TPtr>         this_iterator;
    typedef ntree_iterator<T,TRef,TPtr>                 simple_iterator;

    // constructor to create a null iterator - you must assign a valid value to this iterator before using it
    ntree_postfix_iterator(void);
    ~ntree_postfix_iterator(void);

    // tests
    // a null iterator is one that has not been initialised with a value yet
    // i.e. you just declared it but didn't assign to it
    bool null(void) const;
    // an end iterator is one that points to the end element of the list of nodes
    // in STL conventions this is one past the last valid element and must not be dereferenced
    bool end(void) const;
    // a valid iterator is one that can be dereferenced
    // i.e. non-null and non-end
    bool valid(void) const;

    // Type conversion methods allow const_iterator and iterator to be converted
    // convert an iterator/const_iterator to a const_iterator
    const_iterator constify(void) const;
    iterator deconstify(void) const;

    // generate a simple iterator from a traversal iterator
    simple_iterator simplify(void) const;

    // tests useful for putting iterators into other STL structures and for testing whether iteration has completed
    bool operator == (const this_iterator& r) const;
    bool operator != (const this_iterator& r) const;
    bool operator < (const this_iterator& r) const;

    // increment/decrement operators used to step through the set of all nodes in a graph
    // it is only legal to increment a valid iterator
    // pre-increment
    // exceptions: null_dereference,end_dereference
    this_iterator& operator ++ (void);
    // post-increment
    // exceptions: null_dereference,end_dereference
    this_iterator operator ++ (int);

    // access the node data - a const_iterator gives you a const element, an iterator a non-const element
    // it is illegal to dereference an invalid (i.e. null or end) iterator
    // exceptions: null_dereference,end_dereference
    reference operator*(void) const;
    // exceptions: null_dereference,end_dereference
    pointer operator->(void) const;

    friend class ntree<T>;
    friend class ntree_iterator<T,TRef,TPtr>;

  private:
    simple_iterator m_iterator;

    explicit ntree_postfix_iterator(const simple_iterator& i);
    const simple_iterator& get_iterator(void) const;
    simple_iterator& get_iterator(void);
  };

  ////////////////////////////////////////////////////////////////////////////////
  // The Ntree class
  ////////////////////////////////////////////////////////////////////////////////

  template<typename T>
  class ntree
  {
  public:
    // STL-like typedefs for the types and iterators
    typedef T value_type;

    typedef ntree_iterator<T,T&,T*> iterator;
    typedef ntree_iterator<T,const T&,const T*> const_iterator;

    typedef ntree_prefix_iterator<T,T&,T*> prefix_iterator;
    typedef ntree_prefix_iterator<T,const T&,const T*> const_prefix_iterator;

    typedef ntree_postfix_iterator<T,T&,T*> postfix_iterator;
    typedef ntree_postfix_iterator<T,const T&,const T*> const_postfix_iterator;

    typedef std::vector<iterator> iterator_vector;
    typedef std::vector<const_iterator> const_iterator_vector;

    //////////////////////////////////////////////////////////////////////////////
    // Constructors, destructors and copies

    ntree(void);
    ~ntree(void);

    // copy constructor and assignment both copy the tree
    ntree(const ntree<T>&);
    ntree<T>& operator=(const ntree<T>&);

    //////////////////////////////////////////////////////////////////////////////
    // size tests

    // tests on whole tree
    bool empty(void) const;
    unsigned size(void) const;

    // tests for number of nodes in subtree starting at node
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned size(const const_iterator& node) const;
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned size(const iterator& node);

    // test for depth of tree from root to node
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned depth(const const_iterator& node) const;
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned depth(const iterator& node);

    //////////////////////////////////////////////////////////////////////////////
    // direct traversal

    // get the root node's iterator to start the traversal
    const_iterator root(void) const;
    iterator root(void);

    // get the number of children of this node, so they can be accessed 0..n-1
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned children(const const_iterator& node) const;
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned children(const iterator& node);

    // get the iterator for a child given it's offset into the children array
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    const_iterator child(const const_iterator& node, unsigned child) const;
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    iterator child(const iterator& node, unsigned child);

    // search a node's children array to find a child given its iterator
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned child_offset(const const_iterator& node, const const_iterator& child) const;
    // exceptions: wrong_object,null_dereference,end_dereference
    unsigned child_offset(const iterator& node, const iterator& child);

    // go back up the tree by getting the iterator to a node's parent - the parent of root is null
    // exceptions: wrong_object,null_dereference,end_dereference
    const_iterator parent(const const_iterator& node) const;
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator parent(const iterator& node);

    //////////////////////////////////////////////////////////////////////////////
    // iterator traversal

    const_prefix_iterator prefix_begin(void) const;
    prefix_iterator prefix_begin(void);
    const_prefix_iterator prefix_end(void) const;
    prefix_iterator prefix_end(void);

    const_postfix_iterator postfix_begin(void) const;
    postfix_iterator postfix_begin(void);
    const_postfix_iterator postfix_end(void) const;
    postfix_iterator postfix_end(void);

    //////////////////////////////////////////////////////////////////////////////
    // breadth-first traversal

    const_iterator_vector breadth_first_traversal(void) const;
    iterator_vector breadth_first_traversal(void);

    //////////////////////////////////////////////////////////////////////////////
    // modification

    // insert a node

    // discard previous contents and create a new root node
    iterator insert(const T&);
    // add a new child inserted into the node's children at the specified place
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    iterator insert(const iterator& node, unsigned child, const T&);
    // shortcut for insert at the end i.e. tree.insert(node, node.children(), value)
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator insert(const iterator& node, const T&);
    // old name for the above
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator append(const iterator& node, const T&);

    // insert a copy of a subtree

    // discard previous contents and copy the tree
    iterator insert(const ntree<T>&);
    // add a copy of the tree as a new child inserted into the node's children at the specified place
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    iterator insert(const iterator& node, unsigned child, const ntree<T>&);
    // shortcut for insert at the end i.e. tree.insert(node, node.children(), value)
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator insert(const iterator& node, const ntree<T>&);
    // old name for the above
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator append(const iterator& node, const ntree<T>&);

    // insert the subtree without copying

    // discard previous contents and move the tree without copying
    // invalidates all iterators to the old tree
    iterator move(ntree<T>&);
    // move the tree to become the designated child
    // invalidates all iterators to the old tree
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    iterator move(const iterator& node, unsigned child, ntree<T>&);
    // shortcut for move to the last child i.e. node.move(node, node.children(), value)
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator move(const iterator& node, ntree<T>&);

    // insert/erase in the middle of a tree

    // replace the node with the new value, pushing the old node down to make it the child
    // returns the iterator to the new, pushed node
    // exceptions: wrong_object,null_dereference,end_dereference
    iterator push(const iterator& node, const T&);
    // erases the specified child, moving its children up to become the node's children
    // exceptions: wrong_object,null_dereference,end_dereference
    void pop(const iterator& node, unsigned child);

    // erase nodes and subtrees

    // erase the whole tree
    void erase(void);
    // erase the node and all its children
    // exceptions: wrong_object,null_dereference,end_dereference
    void erase(const iterator& node);
    // erase the specified child
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    void erase_child(const iterator& node, unsigned child);
    // synonym for above
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    void erase(const iterator& node, unsigned child);
    // erase all children of the node, but leave the node
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    void erase_children(const iterator& node);

    // extract a subtree as a copy leaving the original tree unchanged

    // get a copy of the tree as a tree
    ntree<T> subtree(void);
    // get a copy of the subtree as a tree with the specified node as root
    // exceptions: wrong_object,null_dereference,end_dereference
    ntree<T> subtree(const iterator& node);
    // get a copy of the subtree as a tree with the specified child as root
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    ntree<T> subtree(const iterator& node, unsigned child);

    // extract a subtree by moving the contents

    // move the whole tree to make a new tree
    ntree<T> cut(void);
    // move the subtree to make a new tree with the specified node as root
    // exceptions: wrong_object,null_dereference,end_dereference
    ntree<T> cut(const iterator& node);
    // move the subtree to make a new tree with the specified child as root
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    ntree<T> cut(const iterator& node, unsigned child);

    // re-ordering of child nodes

    // reorder the children of a node
    // moves the child at the given offset to the new offset, reordering its siblings to make room
    // this preserves the order of the remaining siblings but not their positions e.g. reorder([a,b,c,d],0,3) = [b,c,d,a]
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    void reorder(const iterator& node, unsigned child_offset, unsigned new_offset);

    // swap two children of a node
    // swaps the child at the given offset with the new offset
    // this preserves the position of the remaining siblings e.g. swap([a,b,c,d],0,3) = [d,b,c,a]
    // exceptions: wrong_object,null_dereference,end_dereference,std::out_of_range
    void swap(const iterator& node, unsigned child1, unsigned child2);

    //////////////////////////////////////////////////////////////////////////////

  private:
    ntree_node<T>* m_root;
  };

  ////////////////////////////////////////////////////////////////////////////////

} // end namespace stlplus

#include "ntree.tpp"
#endif