mirror of
https://github.com/ETLCPP/etl.git
synced 2026-10-28 18:07:10 +08:00
Add docs and tutorial for etl::intrusive_avl_tree (#1608)
* Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: John Wellbelove <jwellbelove@users.noreply.github.com>
This commit is contained in:
parent
14b3f4d335
commit
27ce9364ec
@ -120,6 +120,38 @@ The documentation is written in [Goldmark](https://github.com/teekennedy/goldmar
|
|||||||
Hugo uses [Goldmark](https://github.com/teekennedy/goldmark-markdown) as its default Markdown processor for versions 0.60.0 and newer.
|
Hugo uses [Goldmark](https://github.com/teekennedy/goldmark-markdown) as its default Markdown processor for versions 0.60.0 and newer.
|
||||||
It is built into Hugo, providing high-performance and fully [CommonMark](https://commonmark.org/)-compliant rendering, along with support for [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) Markdown.
|
It is built into Hugo, providing high-performance and fully [CommonMark](https://commonmark.org/)-compliant rendering, along with support for [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) Markdown.
|
||||||
|
|
||||||
|
### Documenting Algorithmic Complexity
|
||||||
|
|
||||||
|
When documenting the complexity of an operation, use the `complexity` shortcode instead of writing raw Big-O text.
|
||||||
|
Place it on its own `**Complexity**` lines, right after the `**Description**` line/paragraph, for example:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Description**
|
||||||
|
Returns the number of linked items.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The shortcode takes two arguments: a complexity class (used to color and label it) and the expression to show
|
||||||
|
inside `O(...)`. The supported classes are:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where N is number of items in the tree, and assuming comparator is {{< complexity "constant" "1" >}}.
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where N is number of items in the tree.
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linearithmic" "N log N" >}}, where N is number of items in the tree.
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "polynomial" "N²" >}}, where N is number of items in the tree.
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}} when something, or
|
||||||
|
{{< complexity "exponential" "2ᴺ" >}} some alternative, where N is number of items in the tree.
|
||||||
|
```
|
||||||
|
|
||||||
### Starting a New Page
|
### Starting a New Page
|
||||||
|
|
||||||
A template file is provided at `docs/page-template.md` to use as a starting point. Copy it to the
|
A template file is provided at `docs/page-template.md` to use as a starting point. Copy it to the
|
||||||
|
|||||||
6
docs/containers/trees/_index.md
Normal file
6
docs/containers/trees/_index.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
title: "Trees"
|
||||||
|
weight: 100
|
||||||
|
---
|
||||||
|
|
||||||
|
Tree like containers.
|
||||||
560
docs/containers/trees/intrusive-avl-tree.md
Normal file
560
docs/containers/trees/intrusive-avl-tree.md
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
---
|
||||||
|
title: "intrusive_avl_tree"
|
||||||
|
weight: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
{{< callout type="info">}}
|
||||||
|
Header: `intrusive_avl_tree.h`
|
||||||
|
Similar to: `std::set` / `std::map` (ordering only, no ownership)
|
||||||
|
{{< /callout >}}
|
||||||
|
|
||||||
|
An intrusive, self-balancing AVL tree.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename TValue, size_t ID_ = 0>
|
||||||
|
etl::intrusive_avl_tree
|
||||||
|
```
|
||||||
|
|
||||||
|
`TValue` is the type that contains the actual values. It must derive from
|
||||||
|
`etl::intrusive_avl_tree_base<ID_>::link_type`, exposed as the tree's own
|
||||||
|
`link_type`. Unlike `etl::intrusive_list`/`etl::intrusive_forward_list`,
|
||||||
|
which are templated directly on a link type and expect the node to derive
|
||||||
|
from it publicly, `intrusive_avl_tree` is templated only on `TValue` and an
|
||||||
|
`ID_`. Its `link_type` is a deliberately opaque wrapper around
|
||||||
|
`etl::tree_link<ID_>` that exposes almost nothing as public (or even
|
||||||
|
protected) API, by design, so that inheriting from it imposes as little
|
||||||
|
"intrusive"-ness as possible, avoids name conflicts, and hides implementation
|
||||||
|
details.
|
||||||
|
|
||||||
|
As with the other intrusive containers, an object may inherit multiple
|
||||||
|
`link_type`s with different `ID_` values to be a member of several trees (or
|
||||||
|
lists) at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
typedef etl::intrusive_avl_tree_base<0>::link_type FirstLink;
|
||||||
|
typedef etl::intrusive_avl_tree_base<1>::link_type SecondLink;
|
||||||
|
|
||||||
|
struct Item : public FirstLink, public SecondLink
|
||||||
|
{
|
||||||
|
int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
etl::intrusive_avl_tree<Item, 0> tree0;
|
||||||
|
etl::intrusive_avl_tree<Item, 1> tree1;
|
||||||
|
```
|
||||||
|
|
||||||
|
See also:
|
||||||
|
[Intrusive Links]({{% relref "../../utilities/intrusive-links.md" %}})
|
||||||
|
[intrusive_list]({{% relref "../lists/intrusive-list.md" %}})
|
||||||
|
[intrusive_forward_list]({{% relref "../lists/intrusive-forward-list.md" %}})
|
||||||
|
|
||||||
|
There is no class-level comparator template parameter. Every ordering
|
||||||
|
operation instead takes its own comparator functor at the call site.
|
||||||
|
Lookup functions (`find`, `lower_bound`, `upper_bound`, `find_or_insert`)
|
||||||
|
take a unary comparator accepting a single `const value_type&` and returning
|
||||||
|
an `int`: `>0` if the search target is "greater" than the argument, `0` if
|
||||||
|
found, `<0` if "smaller". The range constructor takes a binary comparator
|
||||||
|
`int(const value_type& lhs, const value_type& rhs)`; `etl::compare<value_type>::cmp`
|
||||||
|
can be used directly if `value_type` defines `<`.
|
||||||
|
|
||||||
|
## Member types
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
link_type (nested, inherited from etl::intrusive_avl_tree_base<ID_>)
|
||||||
|
value_type TValue
|
||||||
|
pointer value_type*
|
||||||
|
const_pointer const value_type*
|
||||||
|
reference value_type&
|
||||||
|
const_reference const value_type&
|
||||||
|
size_type size_t
|
||||||
|
rvalue_reference value_type&& // C++11
|
||||||
|
```
|
||||||
|
|
||||||
|
## The link type
|
||||||
|
|
||||||
|
`TValue` must derive from `link_type`. It has its own set of special member
|
||||||
|
functions that a node author relies on.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
link_type()
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Default constructor. Leaves the item unlinked.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
link_type(link_type&& other)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Constructs a new item by moving `other` into `this`. After
|
||||||
|
construction, `this` replaces `other` in the same tree position that
|
||||||
|
`other` occupied, so no tree balancing is needed. `other` becomes unlinked.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
link_type& operator=(link_type&& other)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Assigns `other` by moving it into `this`. Does nothing on
|
||||||
|
self-assignment. After assignment, `this` replaces `other` in the same tree
|
||||||
|
position that `other` occupied; `other` becomes unlinked. `this` might end
|
||||||
|
up in a different tree than it started in.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}} if `this` is already linked to a tree (it has to be erased first, with rebalancing), or
|
||||||
|
{{< complexity "constant" "1" >}} if `this` is not linked, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
link_type(const link_type&) = delete;
|
||||||
|
link_type& operator=(const link_type&) = delete;
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Copy construction and assignment are disabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
~link_type()
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Might rotate the tree as necessary to keep it balanced. The tree is not the
|
||||||
|
real owner of its nodes' memory - the node's actual owner may destroy it at
|
||||||
|
any time, with or without a prior explicit `erase`. If the link is still
|
||||||
|
linked to a tree when it's destroyed, the destructor unlinks it
|
||||||
|
automatically; otherwise the node's former parent and children would keep
|
||||||
|
dangling pointers to it, breaking the tree and leading to undefined
|
||||||
|
behaviour. This is what makes it safe to embed a tree node in an object
|
||||||
|
whose lifetime is managed elsewhere.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
etl::intrusive_avl_tree<TValue, ID_>()
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Default constructor. Constructs an empty tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename TIterator, typename TBinaryCompare>
|
||||||
|
etl::intrusive_avl_tree<TValue, ID_>(TIterator first, TIterator last, TBinaryCompare binary_comp)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Constructs the tree from the range [`first`, `last`) of items, using
|
||||||
|
`binary_comp` to order them. All items in the range must be unlinked
|
||||||
|
initially; if any item is already linked to some other tree, throws
|
||||||
|
`etl::intrusive_avl_tree_value_is_already_linked`, and any items already
|
||||||
|
inserted are unlinked again - it's all or nothing.
|
||||||
|
If duplicates should be kept, `binary_comp` should return a non-zero result
|
||||||
|
even for "equal" items - `<0` prepends a duplicate, `>0` appends one.
|
||||||
|
Returning `0` for equal items keeps only the first of each run of duplicates
|
||||||
|
in the range; the rest are left unlinked. `etl::compare<value_type>::cmp`
|
||||||
|
can be used directly as `binary_comp`.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linearithmic" "N log N" >}}, where `N` is number of items in the range.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
intrusive_avl_tree(intrusive_avl_tree&&) = default;
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Move constructor.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
intrusive_avl_tree(const intrusive_avl_tree&) = delete;
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Copy construction is disabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
~intrusive_avl_tree()
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Every remaining node is unlinked; none of the value objects are destroyed,
|
||||||
|
since the tree never owns their memory.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
## Assignment
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
intrusive_avl_tree& operator=(intrusive_avl_tree&&) = default;
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Move assignment. Does nothing on self-assignment. All of `this`
|
||||||
|
tree's former items have to be unlinked.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where `N` is the size of `this` tree before assignment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
intrusive_avl_tree& operator=(const intrusive_avl_tree&) = delete;
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
C++11. Copy assignment is disabled.
|
||||||
|
|
||||||
|
## Capacity
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool empty() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns `true` if the tree has no linked items.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
size_t size() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns the number of linked items.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
## Iterators
|
||||||
|
|
||||||
|
Only bidirectional `iterator`/`const_iterator` are provided; there is no
|
||||||
|
`reverse_iterator` (use `visit_in_order` with `is_reverse = true` instead).
|
||||||
|
An iterator may be in one of three logical states: it references a real
|
||||||
|
node (`has_value() == true`), it is the terminal sentinel (`it == end()`),
|
||||||
|
or it is "valueless" (`!has_value() && it != end()` - e.g. a
|
||||||
|
default-constructed iterator, or the result of `get_parent()`/`get_child()`
|
||||||
|
when there is no such node).
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator begin()
|
||||||
|
const_iterator begin() const
|
||||||
|
const_iterator cbegin() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the smallest item. Returns `end()` if the tree is
|
||||||
|
empty.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator end()
|
||||||
|
const_iterator end() const
|
||||||
|
const_iterator cend() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the terminal sentinel.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator min()
|
||||||
|
const_iterator min() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Equivalent to `begin()`.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator max()
|
||||||
|
const_iterator max() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Equivalent to `--end()`.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator& operator++()
|
||||||
|
iterator operator++(int)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Advances the iterator to the next ("greater") item.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}} amortized,
|
||||||
|
{{< complexity "logarithmic" "log N" >}} worst-case, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator& operator--()
|
||||||
|
iterator operator--(int)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Moves the iterator to the previous ("smaller") item.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}} amortized,
|
||||||
|
{{< complexity "logarithmic" "log N" >}} worst-case, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
reference operator*() const
|
||||||
|
pointer operator->() const
|
||||||
|
pointer get() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Dereferences an iterator that has a value. `operator*`/`operator->` throw
|
||||||
|
`etl::intrusive_avl_tree_iterator_exception` if `has_value()` is `false`.
|
||||||
|
`get()` returns `nullptr` instead of throwing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool has_value() const
|
||||||
|
explicit operator bool() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns `true` if the iterator references a real node.
|
||||||
|
|
||||||
|
## Advanced traversal
|
||||||
|
|
||||||
|
These are normally only needed for advanced traversal.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator get_root()
|
||||||
|
const_iterator get_root() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the root node. Valueless (`has_value() == false`) if
|
||||||
|
the tree is empty.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int_fast8_t get_balance_factor() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns the node's AVL balance factor: `-1`, `0` or `+1`.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator get_parent() const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the node's parent. Valueless if there is no parent.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator get_child(bool is_right) const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the node's left (`is_right == false`) or right
|
||||||
|
(`is_right == true`) child. Valueless if there is no such child.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
## Lookup
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename TCompare>
|
||||||
|
iterator find(TCompare comp)
|
||||||
|
template <typename TCompare>
|
||||||
|
const_iterator find(TCompare comp) const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Finds an item using the unary comparator `comp`. Returns `end()` if there
|
||||||
|
is no matching item.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree,
|
||||||
|
and assuming `comp` is {{< complexity "constant" "1" >}}.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename TCompare>
|
||||||
|
iterator lower_bound(TCompare comp)
|
||||||
|
template <typename TCompare>
|
||||||
|
const_iterator lower_bound(TCompare comp) const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the first item that compares as "not less" than the
|
||||||
|
target of `comp`. Returns `end()` if no such item exists.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree,
|
||||||
|
and assuming `comp` is {{< complexity "constant" "1" >}}.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename TCompare>
|
||||||
|
iterator upper_bound(TCompare comp)
|
||||||
|
template <typename TCompare>
|
||||||
|
const_iterator upper_bound(TCompare comp) const
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Returns an iterator to the first item that compares as "greater" than the
|
||||||
|
target of `comp`. Returns `end()` if no such item exists.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree,
|
||||||
|
and assuming `comp` is {{< complexity "constant" "1" >}}.
|
||||||
|
|
||||||
|
## Modifiers
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename TCompare, typename TFactory>
|
||||||
|
etl::pair<iterator, bool> find_or_insert(TCompare comp, TFactory factory)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Finds an existing item using the unary comparator `comp`; if not found,
|
||||||
|
calls `factory()` (no arguments) and inserts the item it returns at the
|
||||||
|
position where the search stopped. This operation never invalidates
|
||||||
|
existing iterators, though depending on where the new item was linked,
|
||||||
|
existing iterators may skip over it.
|
||||||
|
`factory` must return the address of the new value (castable to
|
||||||
|
`link_type*`). If it returns `nullptr`, the tree is left unmodified and the
|
||||||
|
result iterator is valueless. If the returned value is already linked to
|
||||||
|
any tree, throws `etl::intrusive_avl_tree_value_is_already_linked`.
|
||||||
|
Returns a pair of: an iterator to the found or inserted item (never `end()`,
|
||||||
|
but may be valueless), and a `bool` that is `true` if the tree was modified.
|
||||||
|
If duplicates are wanted, `comp` should return a non-zero result even for
|
||||||
|
"equal" items - `+1` appends after existing duplicates, `-1` prepends
|
||||||
|
before them.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree,
|
||||||
|
and assuming `comp` and `factory` are {{< complexity "constant" "1" >}}.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
iterator erase(iterator position)
|
||||||
|
iterator erase(const_iterator position)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Erases the item at `position`, including tree rebalancing. `position` must
|
||||||
|
reference a real item (asserts/throws
|
||||||
|
`etl::intrusive_avl_tree_iterator_exception` otherwise) and must originate
|
||||||
|
from this tree instance. Invalidates any existing iterator to the erased
|
||||||
|
item, but no others. Returns an iterator to the next node.
|
||||||
|
Use `clear()` instead if every item needs to be erased - no rebalancing is
|
||||||
|
involved.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "logarithmic" "log N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void clear()
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Unlinks every item, leaving the tree empty. Invalidates all existing
|
||||||
|
iterators. This is cheaper than calling `erase` on every item, which would
|
||||||
|
cost {{< complexity "linearithmic" "N log N" >}} once intermediate
|
||||||
|
rebalancing is taken into account.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void swap(intrusive_avl_tree& other)
|
||||||
|
friend void swap(intrusive_avl_tree& lhs, intrusive_avl_tree& rhs)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Swaps the contents of two trees. Does nothing on self-swap.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "constant" "1" >}}
|
||||||
|
|
||||||
|
## Traversal
|
||||||
|
|
||||||
|
Each of these visits every item exactly once, without recursion, and has a
|
||||||
|
`const` overload. The visitor must not modify the tree during visitation.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename Visitor>
|
||||||
|
void visit_in_order(bool is_reverse, Visitor visitor)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Visits every item in sorted order. `is_reverse` selects ascending (`false`)
|
||||||
|
or descending (`true`) order.
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename Visitor>
|
||||||
|
void visit_pre_order(bool is_reverse, Visitor visitor)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Visits every item in pre-order (parent before children).
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename Visitor>
|
||||||
|
void visit_post_order(bool is_reverse, Visitor visitor)
|
||||||
|
```
|
||||||
|
**Description**
|
||||||
|
Visits every item in post-order (children before parent).
|
||||||
|
|
||||||
|
**Complexity**
|
||||||
|
{{< complexity "linear" "N" >}}, where `N` is number of items in the tree.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
intrusive_avl_tree_exception // base exception
|
||||||
|
intrusive_avl_tree_iterator_exception // invalid iterator use
|
||||||
|
intrusive_avl_tree_value_is_already_linked // item already linked to a tree
|
||||||
|
```
|
||||||
@ -13,6 +13,11 @@ A quick introduction to containers.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
[Intrusive AVL Tree](./intrusive-avl-tree-tutorial)
|
||||||
|
Ordering objects that already exist, without a second container.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
[observer](./observer-tutorial)
|
[observer](./observer-tutorial)
|
||||||
A templated set of classes to allow easier implementation of the observer pattern.
|
A templated set of classes to allow easier implementation of the observer pattern.
|
||||||
|
|
||||||
|
|||||||
210
docs/tutorials/intrusive-avl-tree-tutorial.md
Normal file
210
docs/tutorials/intrusive-avl-tree-tutorial.md
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
---
|
||||||
|
title: "Intrusive AVL Tree"
|
||||||
|
---
|
||||||
|
|
||||||
|
A tutorial on how `etl::intrusive_avl_tree` can give ordered,
|
||||||
|
{{< complexity "logarithmic" "log N" >}} access to objects that already
|
||||||
|
exist elsewhere, without a second container to track them.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Imagine a set of driver/sensor tasks, each owned by its own subsystem. They
|
||||||
|
are created and destroyed at unpredictable times as modules attach and
|
||||||
|
detach, so there is no compile-time upper bound on how many exist at once.
|
||||||
|
A scheduler needs to visit the tasks that are currently due, in ascending
|
||||||
|
deadline order, on every tick.
|
||||||
|
|
||||||
|
A normal fixed-capacity ordered container (`etl::set`/`etl::map`) would need
|
||||||
|
a second, separately-sized place to store pointers to these tasks. Instead,
|
||||||
|
embedding an `etl::intrusive_avl_tree` link directly into each task object
|
||||||
|
gives the scheduler ordered, {{< complexity "logarithmic" "log N" >}}
|
||||||
|
access to exactly the tasks that exist right now - nothing more is
|
||||||
|
allocated, and nothing needs to be kept in sync by hand. That's the payoff of "intrusive": the tree links the
|
||||||
|
objects that already exist, rather than storing copies of them.
|
||||||
|
|
||||||
|
The example below also demonstrates two things that fall out of the design
|
||||||
|
for free:
|
||||||
|
- **Move support.** When the `std::vector` that owns the tasks reallocates,
|
||||||
|
every task is moved into new storage - and each move takes over its
|
||||||
|
task's exact tree position, with no rebalancing and no code of its own.
|
||||||
|
- **Safe removal without an explicit `erase()` call.** Removing a task's
|
||||||
|
owning object removes it from the schedule too, with no dangling entry
|
||||||
|
left behind - whether that's because the object is destroyed while still
|
||||||
|
linked (`link_type`'s destructor unlinks it automatically), or, as in
|
||||||
|
this example, because `std::vector::erase` move-assigns another task over
|
||||||
|
it (`link_type`'s move assignment unlinks `this` before taking over the
|
||||||
|
moved-from tree position).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "etl/intrusive_avl_tree.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
// A driver/sensor task that already exists as a long-lived object owned by
|
||||||
|
// its own subsystem. Inheriting from the tree's link_type is all it takes
|
||||||
|
// to make it schedulable - no separate fixed-capacity container is needed
|
||||||
|
// to track which tasks exist.
|
||||||
|
class SensorTask : public etl::intrusive_avl_tree_base<0>::link_type
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SensorTask(uint32_t id_, uint32_t deadline_, uint32_t period_)
|
||||||
|
: id(id_), deadline(deadline_), period(period_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orders primarily by deadline; ties are broken by id so two tasks due
|
||||||
|
// on the same tick still occupy distinct tree positions.
|
||||||
|
struct Locate
|
||||||
|
{
|
||||||
|
uint32_t target_deadline;
|
||||||
|
uint32_t target_id;
|
||||||
|
|
||||||
|
int operator()(const SensorTask& node) const
|
||||||
|
{
|
||||||
|
if (target_deadline != node.deadline)
|
||||||
|
{
|
||||||
|
return (target_deadline < node.deadline) ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (target_id != node.id)
|
||||||
|
{
|
||||||
|
return (target_id < node.id) ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t deadline; // next tick at which this task must be serviced
|
||||||
|
uint32_t period;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef etl::intrusive_avl_tree<SensorTask> Schedule;
|
||||||
|
|
||||||
|
// The only place a task gets linked - called whenever a module attaches,
|
||||||
|
// or whenever a task is rescheduled after being serviced.
|
||||||
|
void schedule_task(Schedule& schedule, SensorTask& task)
|
||||||
|
{
|
||||||
|
schedule.find_or_insert(SensorTask::Locate{ task.deadline, task.id },
|
||||||
|
[&task] { return &task; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visits every currently-due task in ascending deadline order, services it,
|
||||||
|
// then reschedules it at its next deadline.
|
||||||
|
void service_due_tasks(Schedule& schedule, uint32_t now)
|
||||||
|
{
|
||||||
|
for (auto it = schedule.begin(); (it != schedule.end()) && (it->deadline <= now); )
|
||||||
|
{
|
||||||
|
SensorTask& task = *it;
|
||||||
|
it = schedule.erase(it); // O(log N), rebalances
|
||||||
|
|
||||||
|
std::cout << "servicing task " << task.id << " (was due at " << task.deadline << ")\n";
|
||||||
|
|
||||||
|
task.deadline = now + task.period;
|
||||||
|
schedule_task(schedule, task); // reschedule at the new deadline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_schedule(const Schedule& schedule)
|
||||||
|
{
|
||||||
|
for (auto it = schedule.begin(); it != schedule.end(); ++it)
|
||||||
|
{
|
||||||
|
std::cout << " task " << it->id << " due at " << it->deadline << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
Schedule schedule;
|
||||||
|
|
||||||
|
// Driver registry: a plain std::vector that a subsystem uses to own its
|
||||||
|
// tasks for whatever reason it already needs to - the tree does not
|
||||||
|
// require this vector to exist, it's just a stand-in for "wherever the
|
||||||
|
// real owner happens to keep the object".
|
||||||
|
std::vector<SensorTask> driver_registry;
|
||||||
|
driver_registry.reserve(2);
|
||||||
|
|
||||||
|
driver_registry.emplace_back(1, 100, 1000);
|
||||||
|
schedule_task(schedule, driver_registry.back());
|
||||||
|
|
||||||
|
driver_registry.emplace_back(2, 300, 1000);
|
||||||
|
schedule_task(schedule, driver_registry.back());
|
||||||
|
|
||||||
|
std::cout << "Initial schedule (ascending deadline):\n";
|
||||||
|
print_schedule(schedule);
|
||||||
|
|
||||||
|
std::cout << "\nschedule.size() before growth: " << schedule.size() << "\n";
|
||||||
|
|
||||||
|
// driver_registry.reserve()'d capacity was 2; this emplace_back forces a
|
||||||
|
// reallocation. std::vector move-constructs every existing SensorTask
|
||||||
|
// into the new storage. Each move steals its task's exact tree position
|
||||||
|
// (O(1), no rebalancing) - the tree needs no code of its own to survive
|
||||||
|
// the underlying storage moving.
|
||||||
|
driver_registry.emplace_back(3, 200, 1100);
|
||||||
|
schedule_task(schedule, driver_registry.back());
|
||||||
|
|
||||||
|
std::cout << "schedule.size() after growth: " << schedule.size() << "\n\n";
|
||||||
|
|
||||||
|
std::cout << "Schedule after growth (still correctly ordered):\n";
|
||||||
|
print_schedule(schedule);
|
||||||
|
|
||||||
|
std::cout << "\nServicing due tasks at tick 250:\n";
|
||||||
|
service_due_tasks(schedule, 250);
|
||||||
|
|
||||||
|
std::cout << "\nSchedule after servicing:\n";
|
||||||
|
print_schedule(schedule);
|
||||||
|
|
||||||
|
// Unplugging a module removes its SensorTask from driver_registry. Task 2
|
||||||
|
// isn't the last element, so std::vector::erase() doesn't just destroy it
|
||||||
|
// in place: it move-assigns the following element (task 3) into task 2's
|
||||||
|
// slot, then destroys the now-duplicate last slot. That move-assignment
|
||||||
|
// is SensorTask's (implicitly-generated) one, which calls
|
||||||
|
// link_type::operator=(link_type&&): it first unlinks *this* - task 2's
|
||||||
|
// slot, still linked at deadline 300 - from the tree, then takes over
|
||||||
|
// task 3's tree position. The destructor that runs afterwards, on the
|
||||||
|
// vacated last slot, fires on an already-unlinked, moved-from object, so
|
||||||
|
// it has nothing left to do - no explicit schedule.erase() call is made
|
||||||
|
// here, and no dangling entry is left behind.
|
||||||
|
std::cout << "\nschedule.size() before unplugging task 2: " << schedule.size() << "\n";
|
||||||
|
|
||||||
|
auto pos = std::find_if(driver_registry.begin(), driver_registry.end(),
|
||||||
|
[](const SensorTask& t) { return t.id == 2; });
|
||||||
|
driver_registry.erase(pos); // move-assigns task 3 over task 2, unlinking task 2
|
||||||
|
|
||||||
|
std::cout << "schedule.size() after unplugging task 2: " << schedule.size() << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Program output
|
||||||
|
|
||||||
|
```
|
||||||
|
Initial schedule (ascending deadline):
|
||||||
|
task 1 due at 100
|
||||||
|
task 2 due at 300
|
||||||
|
|
||||||
|
schedule.size() before growth: 2
|
||||||
|
schedule.size() after growth: 3
|
||||||
|
|
||||||
|
Schedule after growth (still correctly ordered):
|
||||||
|
task 1 due at 100
|
||||||
|
task 3 due at 200
|
||||||
|
task 2 due at 300
|
||||||
|
|
||||||
|
Servicing due tasks at tick 250:
|
||||||
|
servicing task 1 (was due at 100)
|
||||||
|
servicing task 3 (was due at 200)
|
||||||
|
|
||||||
|
Schedule after servicing:
|
||||||
|
task 2 due at 300
|
||||||
|
task 1 due at 1250
|
||||||
|
task 3 due at 1350
|
||||||
|
|
||||||
|
schedule.size() before unplugging task 2: 3
|
||||||
|
schedule.size() after unplugging task 2: 2
|
||||||
|
```
|
||||||
@ -297,6 +297,8 @@ template <const size_t ID_>
|
|||||||
struct tree_link
|
struct tree_link
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`etl::intrusive_avl_tree` is built on top of `tree_link` - see [intrusive_avl_tree]({{% relref "../containers/trees/intrusive-avl-tree.md" %}}).
|
||||||
|
|
||||||
### Template parameters
|
### Template parameters
|
||||||
```cpp
|
```cpp
|
||||||
const size_t ID_
|
const size_t ID_
|
||||||
|
|||||||
@ -420,4 +420,80 @@ tr td code{
|
|||||||
font-size: 0.8rem !important;
|
font-size: 0.8rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.complexity {
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .complexity {
|
||||||
|
&.constant {
|
||||||
|
color: #7CFFB2;
|
||||||
|
background: rgba(0,255,120,0.08);
|
||||||
|
border-color: rgba(0,255,120,0.25);
|
||||||
|
}
|
||||||
|
&.logarithmic {
|
||||||
|
color: #7CE7FF;
|
||||||
|
background: rgba(0,200,255,0.08);
|
||||||
|
border-color: rgba(0,200,255,0.18);
|
||||||
|
}
|
||||||
|
&.linear {
|
||||||
|
color: #6AB7FF;
|
||||||
|
background: rgba(80,140,255,0.08);
|
||||||
|
border-color: rgba(80,140,255,0.18);
|
||||||
|
}
|
||||||
|
&.linearithmic {
|
||||||
|
color: #B78CFF;
|
||||||
|
background: rgba(140,80,255,0.08);
|
||||||
|
border-color: rgba(140,80,255,0.18);
|
||||||
|
}
|
||||||
|
&.polynomial {
|
||||||
|
color: #FFD36A;
|
||||||
|
background: rgba(255,190,0,0.08);
|
||||||
|
border-color: rgba(255,190,0,0.18);
|
||||||
|
}
|
||||||
|
&.exponential {
|
||||||
|
color: #ff7d9c;
|
||||||
|
background: rgba(255,0,80,0.08);
|
||||||
|
border-color: rgba(255,0,80,0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html:not(.dark) .complexity {
|
||||||
|
&.constant {
|
||||||
|
color: #15803D;
|
||||||
|
background: rgba(34,197,94,0.10);
|
||||||
|
border-color: rgba(34,197,94,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logarithmic {
|
||||||
|
color: #0E7490;
|
||||||
|
background: rgba(6,182,212,0.10);
|
||||||
|
border-color: rgba(6,182,212,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.linear {
|
||||||
|
color: #2563EB;
|
||||||
|
background: rgba(59,130,246,0.10);
|
||||||
|
border-color: rgba(59,130,246,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.linearithmic {
|
||||||
|
color: #7C3AED;
|
||||||
|
background: rgba(139,92,246,0.10);
|
||||||
|
border-color: rgba(139,92,246,0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.polynomial {
|
||||||
|
color: #CA8A04;
|
||||||
|
background: rgba(234,179,8,0.12);
|
||||||
|
border-color: rgba(234,179,8,0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.exponential {
|
||||||
|
color: #DC2626;
|
||||||
|
background: rgba(239,68,68,0.10);
|
||||||
|
border-color: rgba(239,68,68,0.22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
3
hugo/layouts/shortcodes/complexity.html
Normal file
3
hugo/layouts/shortcodes/complexity.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<span class="complexity {{ .Get 0 }}">
|
||||||
|
𝐎({{ .Get 1 }})
|
||||||
|
</span>
|
||||||
Loading…
x
Reference in New Issue
Block a user