Containers (STL-compatible) StateMachines MessageBus and more for Embedded Systems. See www.etlcpp.com
Diff: intrusive_list.h
- Revision:
- 0:b47c2a7920c2
diff -r 000000000000 -r b47c2a7920c2 intrusive_list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/intrusive_list.h Fri Mar 16 16:34:18 2018 +0000 @@ -0,0 +1,1081 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +http://www.etlcpp.com + +Copyright(c) 2016 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef __ETL_INTRUSIVE_LIST__ +#define __ETL_INTRUSIVE_LIST__ + +#include "platform.h" + +#ifdef ETL_COMPILER_MICROSOFT +#undef min +#endif + +#include <iterator> +#include <algorithm> +#include <functional> +#include <stddef.h> + +#include "platform.h" +#include "nullptr.h" +#include "type_traits.h" +#include "exception.h" +#include "error_handler.h" +#include "intrusive_links.h" +#include "static_assert.h" +#include "algorithm.h" + +#undef ETL_FILE +#define ETL_FILE "21" + +namespace etl +{ + //*************************************************************************** + /// Exception for the intrusive_list. + ///\ingroup intrusive_list + //*************************************************************************** + class intrusive_list_exception : public exception + { + public: + + intrusive_list_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Empty exception for the intrusive_list. + ///\ingroup intrusive_list + //*************************************************************************** + class intrusive_list_empty : public intrusive_list_exception + { + public: + + intrusive_list_empty(string_type file_name_, numeric_type line_number_) + : intrusive_list_exception(ETL_ERROR_TEXT("intrusive_list:empty", ETL_FILE"A"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Iterator exception for the intrusive_list. + ///\ingroup intrusive_list + //*************************************************************************** + class intrusive_list_iterator_exception : public intrusive_list_exception + { + public: + + intrusive_list_iterator_exception(string_type file_name_, numeric_type line_number_) + : intrusive_list_exception(ETL_ERROR_TEXT("intrusive_list:iterator", ETL_FILE"B"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Unsorted exception for the intrusive_list. + ///\ingroup intrusive_list + //*************************************************************************** + class intrusive_list_unsorted : public intrusive_list_exception + { + public: + + intrusive_list_unsorted(string_type file_name_, numeric_type line_number_) + : intrusive_list_exception(ETL_ERROR_TEXT("intrusive_list:unsorted", ETL_FILE"C"), file_name_, line_number_) + { + } + }; + + //*************************************************************************** + /// Base for intrusive list. + ///\ingroup intrusive_list + //*************************************************************************** + template <typename TLink> + class intrusive_list_base + { + public: + + // Node typedef. + typedef TLink link_type; + + //************************************************************************* + /// Assigns a range of values to the intrusive_list. + /// If ETL_THROW_EXCEPTIONS & ETL_DEBUG are defined emits a + /// intrusive_list_iterator_exception if the iterators are reversed. + //************************************************************************* + template <typename TIterator> + void assign(TIterator first, TIterator last) + { +#if defined(ETL_DEBUG) + intmax_t d = std::distance(first, last); + ETL_ASSERT(d >= 0, ETL_ERROR(intrusive_list_iterator_exception)); +#endif + + initialise(); + + link_type* p_last_link = &terminal_link; + + // Add all of the elements. + while (first != last) + { + link_type& link = *first++; + etl::link_splice<link_type>(p_last_link, link); + p_last_link = &link; + ++current_size; + } + } + + //************************************************************************* + /// Pushes a value to the front of the intrusive_list. + //************************************************************************* + void push_front(link_type& value) + { + insert_link(terminal_link, value); + } + + //************************************************************************* + /// Removes a value from the front of the intrusive_list. + //************************************************************************* + void pop_front() + { +#if defined(ETL_CHECK_PUSH_POP) + ETL_ASSERT(!empty(), ETL_ERROR(intrusive_list_empty)); +#endif + remove_link(get_head()); + } + + //************************************************************************* + /// Pushes a value to the back of the intrusive_list. + //************************************************************************* + void push_back(link_type& value) + { + insert_link(terminal_link.link_type::etl_previous, value); + } + + //************************************************************************* + /// Removes a value from the back of the intrusive_list. + //************************************************************************* + void pop_back() + { +#if defined(ETL_CHECK_PUSH_POP) + ETL_ASSERT(!empty(), ETL_ERROR(intrusive_list_empty)); +#endif + remove_link(get_tail()); + } + + //************************************************************************* + /// Clears the intrusive_list. + //************************************************************************* + void clear() + { + initialise(); + } + + //************************************************************************* + /// Reverses the list. + //************************************************************************* + void reverse() + { + if (is_trivial_list()) + { + return; + } + + link_type* pnode = terminal_link.etl_next; + + while (pnode != &terminal_link) + { + pnode->reverse(); + pnode = pnode->etl_previous; // Now we've reversed it, we must go to the previous node. + } + + // Terminal node. + pnode->reverse(); + } + + //************************************************************************* + /// Returns true if the list has no elements. + //************************************************************************* + bool empty() const + { + return (terminal_link.link_type::etl_next == &terminal_link); + } + + //************************************************************************* + /// Returns the number of elements. + //************************************************************************* + size_t size() const + { + return current_size; + } + + protected: + + /// The link that acts as the intrusive_list start & end. + link_type terminal_link; + + size_t current_size; ///< Counts the number of elements in the list. + + //************************************************************************* + /// Is the intrusive_list a trivial length? + //************************************************************************* + bool is_trivial_list() const + { + return (terminal_link.link_type::etl_next == &terminal_link) || (terminal_link.link_type::etl_next->etl_next == &terminal_link); + } + + //************************************************************************* + /// Insert a link. + //************************************************************************* + void insert_link(link_type& previous, link_type& new_link) + { + // Connect to the intrusive_list. + etl::link_splice<link_type>(previous, new_link); + ++current_size; + } + + //************************************************************************* + /// Insert a link. + //************************************************************************* + void insert_link(link_type* previous, link_type& new_link) + { + // Connect to the intrusive_list. + etl::link_splice<link_type>(previous, new_link); + ++current_size; + } + + //************************************************************************* + /// Insert a link. + //************************************************************************* + void insert_link(link_type& previous, link_type* new_link) + { + // Connect to the intrusive_list. + etl::link_splice<link_type>(previous, new_link); + ++current_size; + } + + //************************************************************************* + /// Insert a link. + //************************************************************************* + void insert_link(link_type* previous, link_type* new_link) + { + // Connect to the intrusive_list. + etl::link_splice<link_type>(previous, new_link); + ++current_size; + } + + //************************************************************************* + /// Remove a link. + //************************************************************************* + void remove_link(link_type& link) + { + etl::unlink<link_type>(link); + --current_size; + } + + //************************************************************************* + /// Remove a link. + //************************************************************************* + void remove_link(link_type* link) + { + etl::unlink<link_type>(*link); + --current_size; + } + + //************************************************************************* + /// Get the head link. + //************************************************************************* + link_type* get_head() + { + return terminal_link.etl_next; + } + + //************************************************************************* + /// Get the head link. + //************************************************************************* + const link_type* get_head() const + { + return terminal_link.etl_next; + } + + //************************************************************************* + /// Get the tail link. + //************************************************************************* + link_type* get_tail() + { + return terminal_link.etl_previous; + } + + //************************************************************************* + /// Get the tail link. + //************************************************************************* + const link_type* get_tail() const + { + return terminal_link.etl_previous; + } + + //************************************************************************* + /// Initialise the intrusive_list. + //************************************************************************* + void initialise() + { + etl::link(terminal_link, terminal_link); + current_size = 0; + } + }; + + //*************************************************************************** + /// An intrusive list. + ///\ingroup intrusive_list + ///\note TLink must be a base of TValue. + //*************************************************************************** + template <typename TValue, typename TLink = etl::bidirectional_link<0> > + class intrusive_list : public etl::intrusive_list_base<TLink> + { + public: + + // Node typedef. + typedef typename etl::intrusive_list_base<TLink>::link_type link_type; + + typedef intrusive_list<TValue, TLink> list_type; + + // STL style typedefs. + typedef TValue value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef size_t size_type; + + //************************************************************************* + /// iterator. + //************************************************************************* + class iterator : public std::iterator<std::bidirectional_iterator_tag, value_type> + { + public: + + friend class intrusive_list; + + iterator() + : p_value(std::nullptr) + { + } + + iterator(value_type& value) + : p_value(&value) + { + } + + iterator(const iterator& other) + : p_value(other.p_value) + { + } + + iterator& operator ++() + { + // Read the appropriate 'etl_next'. + p_value = static_cast<value_type*>(p_value->link_type::etl_next); + return *this; + } + + iterator operator ++(int) + { + iterator temp(*this); + // Read the appropriate 'etl_next'. + p_value = static_cast<value_type*>(p_value->link_type::etl_next); + return temp; + } + + iterator& operator --() + { + // Read the appropriate 'etl_previous'. + p_value = static_cast<value_type*>(p_value->link_type::etl_previous); + return *this; + } + + iterator operator --(int) + { + iterator temp(*this); + // Read the appropriate 'etl_previous'. + p_value = static_cast<value_type*>(p_value->link_type::etl_previous); + return temp; + } + + iterator operator =(const iterator& other) + { + p_value = other.p_value; + return *this; + } + + reference operator *() + { + return *p_value; + } + + const_reference operator *() const + { + return *p_value; + } + + pointer operator &() + { + return p_value; + } + + const_pointer operator &() const + { + return p_value; + } + + pointer operator ->() + { + return p_value; + } + + const_pointer operator ->() const + { + return p_value; + } + + friend bool operator == (const iterator& lhs, const iterator& rhs) + { + return lhs.p_value == rhs.p_value; + } + + friend bool operator != (const iterator& lhs, const iterator& rhs) + { + return !(lhs == rhs); + } + + private: + + value_type* p_value; + }; + + //************************************************************************* + /// const_iterator + //************************************************************************* + class const_iterator : public std::iterator<std::bidirectional_iterator_tag, const value_type> + { + public: + + friend class intrusive_list; + + const_iterator() + : p_value(std::nullptr) + { + } + + const_iterator(const value_type& value) + : p_value(&value) + { + } + + const_iterator(const typename intrusive_list::iterator& other) + : p_value(other.p_value) + { + } + + const_iterator(const const_iterator& other) + : p_value(other.p_value) + { + } + + const_iterator& operator ++() + { + // Read the appropriate 'etl_next'. + p_value = static_cast<value_type*>(p_value->link_type::etl_next); + return *this; + } + + const_iterator operator ++(int) + { + const_iterator temp(*this); + // Read the appropriate 'etl_next'. + p_value = static_cast<value_type*>(p_value->link_type::etl_next); + return temp; + } + + const_iterator& operator --() + { + // Read the appropriate 'etl_previous'. + p_value = static_cast<value_type*>(p_value->link_type::etl_previous); + return *this; + } + + const_iterator operator --(int) + { + const_iterator temp(*this); + // Read the appropriate 'etl_previous'. + p_value = static_cast<value_type*>(p_value->link_type::etl_previous); + return temp; + } + + const_iterator operator =(const const_iterator& other) + { + p_value = other.p_value; + return *this; + } + + const_reference operator *() const + { + return *p_value; + } + + const_pointer operator &() const + { + return p_value; + } + + const_pointer operator ->() const + { + return p_value; + } + + friend bool operator == (const const_iterator& lhs, const const_iterator& rhs) + { + return lhs.p_value == rhs.p_value; + } + + friend bool operator != (const const_iterator& lhs, const const_iterator& rhs) + { + return !(lhs == rhs); + } + + private: + + const value_type* p_value; + }; + + typedef typename std::iterator_traits<iterator>::difference_type difference_type; + + //************************************************************************* + /// Constructor. + //************************************************************************* + intrusive_list() + { + this->initialise(); + } + + //************************************************************************* + /// Destructor. + //************************************************************************* + ~intrusive_list() + { + this->clear(); + } + + //************************************************************************* + /// Constructor from range + //************************************************************************* + template <typename TIterator> + intrusive_list(TIterator first, TIterator last) + { + this->assign(first, last); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_list. + //************************************************************************* + iterator begin() + { + return iterator(static_cast<value_type&>(*this->get_head())); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_list. + //************************************************************************* + const_iterator begin() const + { + return const_iterator(static_cast<const value_type&>(*this->get_head())); + } + + //************************************************************************* + /// Gets the beginning of the intrusive_list. + //************************************************************************* + const_iterator cbegin() const + { + return const_iterator(static_cast<const value_type&>(*this->get_head())); + } + + //************************************************************************* + /// Gets the end of the intrusive_list. + //************************************************************************* + iterator end() + { + return iterator(static_cast<value_type&>(this->terminal_link)); + } + + //************************************************************************* + /// Gets the end of the intrusive_list. + //************************************************************************* + const_iterator end() const + { + return const_iterator(static_cast<const value_type&>(this->terminal_link)); + } + + //************************************************************************* + /// Gets the end of the intrusive_list. + //************************************************************************* + const_iterator cend() const + { + return const_iterator(static_cast<const value_type&>(this->terminal_link)); + } + + //************************************************************************* + /// Gets a reference to the first element. + //************************************************************************* + reference front() + { + return *static_cast<value_type*>(this->get_head()); + } + + //************************************************************************* + /// Gets a const reference to the first element. + //************************************************************************* + const_reference front() const + { + return *static_cast<const value_type*>(this->get_head());; + } + + //************************************************************************* + /// Gets a reference to the last element. + //************************************************************************* + reference back() + { + return *static_cast<value_type*>(this->get_tail()); + } + + //************************************************************************* + /// Gets a const reference to the last element. + //************************************************************************* + const_reference back() const + { + return *static_cast<const value_type*>(this->get_tail());; + } + + //************************************************************************* + /// Inserts a value to the intrusive_list before the specified position. + //************************************************************************* + iterator insert(iterator position, value_type& value) + { + this->insert_link(position.p_value->link_type::etl_previous, value); + return iterator(value); + } + + //************************************************************************* + /// Inserts a range of values to the intrusive_list after the specified position. + //************************************************************************* + template <typename TIterator> + void insert(iterator position, TIterator first, TIterator last) + { + while (first != last) + { + // Set up the next free link. + this->insert_link(*position.p_value->link_type::etl_previous, *first++); + } + } + + //************************************************************************* + /// Erases the value at the specified position. + //************************************************************************* + iterator erase(iterator position) + { + iterator next(position); + ++next; + + this->remove_link(*position.p_value); + + return next; + } + + //************************************************************************* + /// Erases a range of elements. + /// Clears the links after erasing if AUTO or CHECKED. + //************************************************************************* + iterator erase(iterator first, iterator last) + { + link_type* p_first = first.p_value; + link_type* p_last = last.p_value; + + // Join the ends. + etl::link<link_type>(p_first->etl_previous, p_last); + + this->current_size -= std::distance(first, last); + + if (p_last == &this->terminal_link) + { + return end(); + } + else + { + return iterator(*static_cast<value_type*>(p_last)); + } + } + + //************************************************************************* + /// Removes all but the one element from every consecutive group of equal + /// elements in the container. + //************************************************************************* + template <typename TIsEqual> + void unique(TIsEqual isEqual) + { + if (this->empty()) + { + return; + } + + iterator i_item = begin(); + ++i_item; + iterator i_previous = begin(); + + while (i_item != end()) + { + if (isEqual(*i_previous, *i_item)) + { + i_item = erase(i_item); + } + else + { + i_previous = i_item; + ++i_item; + } + } + } + + //************************************************************************* + /// Sort using in-place merge sort algorithm. + //************************************************************************* + void sort() + { + sort(std::less<value_type>()); + } + + //************************************************************************* + /// Sort using in-place merge sort algorithm. + /// Uses a supplied predicate function or functor. + /// This is not my algorithm. I got it off the web somewhere. + //************************************************************************* + template <typename TCompare> + void sort(TCompare compare) + { + iterator i_left; + iterator i_right; + iterator i_node; + iterator i_head; + iterator i_tail; + int list_size = 1; + int number_of_merges; + int left_size; + int right_size; + + if (this->is_trivial_list()) + { + return; + } + + while (true) + { + i_left = begin(); + i_head = end(); + i_tail = end(); + + number_of_merges = 0; // Count the number of merges we do in this pass. + + while (i_left != end()) + { + ++number_of_merges; // There exists a merge to be done. + i_right = i_left; + left_size = 0; + + // Step 'list_size' places along from left + for (int i = 0; i < list_size; ++i) + { + ++left_size; + ++i_right; + + if (i_right == end()) + { + break; + } + } + + // If right hasn't fallen off end, we have two lists to merge. + right_size = list_size; + + // Now we have two lists. Merge them. + while (left_size > 0 || (right_size > 0 && i_right != end())) + { + // Decide whether the next node of merge comes from left or right. + if (left_size == 0) + { + // Left is empty. The node must come from right. + i_node = i_right++; + --right_size; + } + else if (right_size == 0 || i_right == end()) + { + // Right is empty. The node must come from left. + i_node = i_left++; + --left_size; + } + else if (!compare(*i_right, *i_left)) + { + // First node of left is lower or same. The node must come from left. + i_node = i_left++; + --left_size; + } + else + { + // First node of right is lower. The node must come from right. + i_node = i_right; + ++i_right; + --right_size; + } + + // Add the next node to the merged head. + if (i_head == end()) + { + etl::link<link_type>(i_head.p_value, i_node.p_value); + i_head = i_node; + i_tail = i_node; + } + else + { + etl::link<link_type>(i_tail.p_value, i_node.p_value); + i_tail = i_node; + } + + etl::link<link_type>(i_tail.p_value, this->terminal_link); + } + + // Now left has stepped `list_size' places along, and right has too. + i_left = i_right; + } + + // If we have done only one merge, we're finished. + if (number_of_merges <= 1) // Allow for number_of_merges == 0, the empty head case + { + return; + } + + // Otherwise repeat, merging lists twice the size + list_size *= 2; + } + } + + //************************************************************************* + // Removes the values specified. + //************************************************************************* + void remove(const_reference value) + { + iterator i_item = begin(); + + while (i_item != end()) + { + if (*i_item == value) + { + i_item = erase(i_item); + } + else + { + ++i_item; + } + } + } + + //************************************************************************* + /// Removes according to a predicate. + //************************************************************************* + template <typename TPredicate> + void remove_if(TPredicate predicate) + { + iterator i_item = begin(); + + while (i_item != end()) + { + if (predicate(*i_item)) + { + i_item = erase(i_item); + } + else + { + ++i_item; + } + } + } + + //************************************************************************* + /// Splice another list into this one. + //************************************************************************* + void splice(iterator position, list_type& other) + { + // No point splicing to ourself! + if (&other != this) + { + if (!other.empty()) + { + link_type& first = *other.get_head(); + link_type& last = *other.get_tail(); + + if (&other != this) + { + this->current_size += other.size(); + } + + link_type& after = *position.p_value; + link_type& before = *after.etl_previous; + + etl::link<link_type>(before, first); + etl::link<link_type>(last, after); + + other.initialise(); + } + } + } + + //************************************************************************* + /// Splice an element from another list into this one. + //************************************************************************* + void splice(iterator position, list_type& other, iterator isource) + { + link_type& before = *position.p_value->link_type::etl_previous; + + etl::unlink<link_type>(*isource.p_value); + etl::link_splice<link_type>(before, *isource.p_value); + + if (&other != this) + { + ++this->current_size; + --other.current_size; + } + } + + //************************************************************************* + /// Splice a range of elements from another list into this one. + //************************************************************************* + void splice(iterator position, list_type& other, iterator begin_, iterator end_) + { + if (!other.empty()) + { + if (&other != this) + { + size_t n = std::distance(begin_, end_); + this->current_size += n; + other.current_size -= n; + } + + link_type& first = *begin_.p_value; + link_type& last = *end_.p_value->link_type::etl_previous; + + // Unlink from the source list. + etl::unlink(first, last); + + // Fix our links. + link_type& before = *position.p_value->link_type::etl_previous; + + etl::link_splice<link_type>(before, first, last); + } + } + + //************************************************************************* + /// Merge another list into this one. Both lists should be sorted. + //************************************************************************* + void merge(list_type& other) + { + merge(other, std::less<value_type>()); + } + + //************************************************************************* + /// Merge another list into this one. Both lists should be sorted. + //************************************************************************* + template <typename TCompare> + void merge(list_type& other, TCompare compare) + { + if (!other.empty()) + { +#if defined(ETL_DEBUG) + ETL_ASSERT(etl::is_sorted(other.begin(), other.end(), compare), ETL_ERROR(intrusive_list_unsorted)); + ETL_ASSERT(etl::is_sorted(begin(), end(), compare), ETL_ERROR(intrusive_list_unsorted)); +#endif + + value_type* other_begin = static_cast<value_type*>(other.get_head()); + value_type* other_end = static_cast<value_type*>(&other.terminal_link); + + value_type* this_begin = static_cast<value_type*>(this->get_head()); + value_type* this_end = static_cast<value_type*>(&this->terminal_link); + + while ((this_begin != this_end) && (other_begin != other_end)) + { + // Find the place to insert. + while ((this_begin != this_end) && !(compare(*other_begin, *this_begin))) + { + this_begin = static_cast<value_type*>(this_begin->link_type::etl_next); + } + + // Insert. + if (this_begin != this_end) + { + while ((other_begin != other_end) && (compare(*other_begin, *this_begin))) + { + value_type* value = other_begin; + other_begin = static_cast<value_type*>(other_begin->link_type::etl_next); + etl::link_splice<link_type>(*this_begin->link_type::etl_previous, *value); + } + } + } + + // Any left over? + if ((this_begin == this_end) && (other_begin != other_end)) + { + etl::link_splice<link_type>(*this->get_tail(), *other_begin, *other_end->link_type::etl_previous); + } + + this->current_size += other.size(); + + other.initialise(); + } + } + + private: + + // Disabled. + intrusive_list(const intrusive_list& other); + intrusive_list& operator = (const intrusive_list& rhs); + }; +} + +#ifdef ETL_COMPILER_MICROSOFT +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#undef ETL_FILE + +#endif +