├── .travis.yml ├── LICENSE ├── Makefile ├── avl_array.h ├── readme.md └── test ├── catch.hpp └── test_suite.cpp /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use a C++11 distro 2 | dist: trusty 3 | sudo: required 4 | 5 | # Enable C++ support 6 | language: cpp 7 | 8 | # Compiler selection 9 | compiler: gcc 10 | 11 | env: 12 | global: 13 | # coverity key 14 | - secure: "MQNBwhFpj7NaDbN6NujsXQGRMpBYHvGtcjLXY9gCXhLUw+Ex40ArBEy1IEQAol7aGtFZiTZDijH1ihL4itzVmu4Bo2sx5WdMGRFS5tp/t4zFFiqpmmtbkYOUHY8wAp3xyxV2NL2EU0KlDloNvFd/4tY2opklUB3eTF+7TQ5CmwWRFGOf2GpGpmQ3+KS1ANhrxdn9sQIP8rSU+JD1/zwrAgeEN7RAxB23t9AU9MeGsOW3CCp3te8vrgNJ/xnOh7T4F/8hQ4lA5iCqqf7OUvcWOif3uBPzlckBTmQ6ylMvOncslj6TBkPJOd33MOmZcWHiPPEAvcXQlzERaLqgHLIg5A/0aTVo27J4iImHbwE61L2JhWvrx5mPkKz/9aTpZyYyQL1hXInk8R9VJ5+JENA5+maF+KNvdf1Zp2Foa3BLFFdRl5thpgyafvcEAZ/7KJ1BC3u4OgTBhh9Tw+1UMFXB9W9+2W2iYUIun8BunDny1aLmnp/t7O1auXjqW2lAWHtH86v9NickDupzkw1klN3Ac+hmfBDffyxUjE2LlmjpC20z2uwKrZCT7XRhalYstPPIwQ6uGr6TVZjqq6mzEjpmmF93U8WYx7iWBJmen0CHbkjY3n8dbUe7aiXQHwjbXFZ8MsWszFvr9eicJOcAOxXKK5jOw4uQSER6NI2H04rwbIs=" 15 | 16 | # addons 17 | addons: 18 | apt: 19 | packages: 20 | - gcc-6 21 | - g++-6 22 | sources: 23 | - ubuntu-toolchain-r-test 24 | 25 | coverity_scan: 26 | project: 27 | name: "mpaland/avl_array" 28 | description: "" 29 | notification_email: marco@paland.com 30 | build_command_prepend: "make clean" 31 | build_command: "make" 32 | branch_pattern: master 33 | 34 | before_install: 35 | # install coveralls 36 | - pip install --user cpp-coveralls 37 | # connect coverity 38 | - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- 39 | 40 | # Active branches 41 | branches: 42 | only: 43 | - master 44 | 45 | script: 46 | # Link gcc-6 and g++-6 to their standard commands 47 | - sudo rm /usr/bin/gcc 48 | - sudo rm /usr/bin/g++ 49 | - sudo ln -s /usr/bin/gcc-6 /usr/bin/gcc 50 | - sudo ln -s /usr/bin/g++-6 /usr/bin/g++ 51 | # Export CC and CXX 52 | - export CC=/usr/bin/gcc-6 53 | - export CXX=/usr/bin/g++-6 54 | # Check versions of gcc, g++ 55 | - gcc -v && g++ -v 56 | # Run build commands 57 | - make 58 | # execute the text suite 59 | - bin/test_suite -d yes 60 | # coverall profiling 61 | - tmp/cov/test_suite 62 | 63 | after_success: 64 | # Report to coveralls 65 | - coveralls --build-root ${TRAVIS_BUILD_DIR} --include avl_array.h --gcov 'gcov-6' --gcov-options '\-lp' 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marco Paland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # 3 | # Generic Makefile 4 | # 5 | # Copyright Marco Paland 2007 - 2017 6 | # Distributed under the MIT License 7 | # 8 | # ------------------------------------------------------------------------------ 9 | 10 | # ------------------------------------------------------------------------------ 11 | # Paths 12 | # ------------------------------------------------------------------------------ 13 | PATH_TOOLS_CC = /usr/bin/ 14 | PATH_TOOLS_CC_LIB = /usr/lib/ 15 | PATH_TOOLS_UTIL = 16 | 17 | PATH_BIN = bin 18 | PATH_TMP = tmp 19 | PATH_NUL = /dev/null 20 | PATH_OBJ = $(PATH_TMP)/obj 21 | PATH_LST = $(PATH_TMP)/lst 22 | PATH_ERR = $(PATH_TMP)/err 23 | PATH_PRE = $(PATH_TMP)/pre 24 | PATH_COV = $(PATH_TMP)/cov 25 | 26 | 27 | # ------------------------------------------------------------------------------ 28 | # Application to build 29 | # ------------------------------------------------------------------------------ 30 | 31 | APP = test_suite 32 | 33 | 34 | # ----------------------------------------------------------------------------- 35 | # Project file list 36 | # Format is: 37 | #FILES_PRJ = file1 \ 38 | # foo/file2 \ 39 | # bar/file3 40 | # ----------------------------------------------------------------------------- 41 | 42 | FILES_PRJ = test/test_suite 43 | 44 | 45 | # ------------------------------------------------------------------------------ 46 | # Additional include files and compiler defines 47 | # Format is: 48 | # C_INCLUDES = -Iinclude_path1 \ 49 | -Iinclude_path2 \ 50 | -Iinclude_path3 \ 51 | # ------------------------------------------------------------------------------ 52 | 53 | C_INCLUDES = 54 | 55 | C_DEFINES = 56 | 57 | 58 | # ------------------------------------------------------------------------------ 59 | # The target name and location 60 | # ------------------------------------------------------------------------------ 61 | TRG = $(PATH_BIN)/$(APP) 62 | 63 | 64 | # ------------------------------------------------------------------------------ 65 | # object files 66 | # ------------------------------------------------------------------------------ 67 | FILES_TMP = $(FILES_PRJ) 68 | FILES_O = $(addsuffix .o, $(FILES_TMP)) 69 | 70 | 71 | # ------------------------------------------------------------------------------ 72 | # VPATH definition 73 | # 74 | # VPATH is required for the maker to find the C-/ASM-Source files. 75 | # Extract the directory/module names from the file list with the dir 76 | # command and remove the duplicated directory names with the sort command. 77 | # FILES_PRJ is listed first to make sure that the source files in the project 78 | # directory are searched first. 79 | # ------------------------------------------------------------------------------ 80 | VPATH := $(sort $(dir $(FILES_TMP))) 81 | 82 | 83 | # ------------------------------------------------------------------------------ 84 | # Development tools 85 | # ------------------------------------------------------------------------------ 86 | AR = $(PATH_TOOLS_CC)ar 87 | AS = $(PATH_TOOLS_CC)g++ 88 | CC = $(PATH_TOOLS_CC)g++ 89 | CL = $(PATH_TOOLS_CC)g++ 90 | NM = $(PATH_TOOLS_CC)nm 91 | GCOV = $(PATH_TOOLS_CC)gcov 92 | OBJDUMP = $(PATH_TOOLS_CC)objdump 93 | OBJCOPY = $(PATH_TOOLS_CC)objcopy 94 | READELF = $(PATH_TOOLS_CC)readelf 95 | SIZE = $(PATH_TOOLS_CC)size 96 | 97 | ECHO = $(PATH_TOOLS_UTIL)echo 98 | MAKE = $(PATH_TOOLS_UTIL)make 99 | MKDIR = $(PATH_TOOLS_UTIL)mkdir 100 | RM = $(PATH_TOOLS_UTIL)rm 101 | SED = $(PATH_TOOLS_UTIL)sed 102 | 103 | 104 | # ------------------------------------------------------------------------------ 105 | # Compiler flags for the target architecture 106 | # ------------------------------------------------------------------------------ 107 | 108 | GCCFLAGS = $(C_INCLUDES) \ 109 | $(C_DEFINES) \ 110 | -std=c++11 \ 111 | -g \ 112 | -Wall \ 113 | -pedantic \ 114 | -Wmain \ 115 | -Wundef \ 116 | -Wsign-conversion \ 117 | -Wuninitialized \ 118 | -Wshadow \ 119 | -Wunreachable-code \ 120 | -Wswitch-default \ 121 | -Wswitch \ 122 | -Wcast-align \ 123 | -Wmissing-include-dirs \ 124 | -Winit-self \ 125 | -Wdouble-promotion \ 126 | -gdwarf-2 \ 127 | -fno-exceptions \ 128 | -O2 \ 129 | -ffunction-sections \ 130 | -ffat-lto-objects \ 131 | -fdata-sections \ 132 | -fverbose-asm \ 133 | -Wextra \ 134 | -Wunused-parameter \ 135 | -Wfloat-equal 136 | 137 | CFLAGS = $(GCCFLAGS) \ 138 | -Wunsuffixed-float-constants \ 139 | -x c \ 140 | -std=c99 141 | 142 | CPPFLAGS = $(GCCFLAGS) \ 143 | -x c++ \ 144 | -fno-rtti \ 145 | -fstrict-enums \ 146 | -fno-use-cxa-atexit \ 147 | -fno-use-cxa-get-exception-ptr \ 148 | -fno-nonansi-builtins \ 149 | -fno-threadsafe-statics \ 150 | -fno-enforce-eh-specs \ 151 | -ftemplate-depth-64 \ 152 | -fexceptions 153 | 154 | AFLAGS = $(GCCFLAGS) \ 155 | -x assembler 156 | 157 | LFLAGS = $(GCCFLAGS) \ 158 | -x none \ 159 | -Wl,--gc-sections 160 | 161 | # ------------------------------------------------------------------------------ 162 | # Targets 163 | # ------------------------------------------------------------------------------ 164 | 165 | # ------------------------------------------------------------------------------ 166 | # Main-Dependencies (app: all) 167 | # ------------------------------------------------------------------------------ 168 | .PHONY: all 169 | all: clean_prj $(TRG) $(TRG)_nm.txt 170 | 171 | 172 | # ------------------------------------------------------------------------------ 173 | # Main-Dependencies (app: rebuild) 174 | # ------------------------------------------------------------------------------ 175 | .PHONY: rebuild 176 | rebuild: clean $(TRG) $(TRG)_nm.txt 177 | 178 | 179 | # ------------------------------------------------------------------------------ 180 | # clean project 181 | # ------------------------------------------------------------------------------ 182 | .PHONY: clean_prj 183 | clean_prj: 184 | @-$(ECHO) +++ cleaning project 185 | @-$(RM) -rf $(PATH_BIN) 2> $(PATH_NUL) 186 | @-$(MKDIR) -p $(PATH_BIN) 187 | @-$(MKDIR) -p $(PATH_OBJ) 188 | @-$(MKDIR) -p $(PATH_ERR) 189 | @-$(MKDIR) -p $(PATH_LST) 190 | @-$(MKDIR) -p $(PATH_PRE) 191 | @-$(MKDIR) -p $(PATH_COV) 192 | 193 | 194 | # ------------------------------------------------------------------------------ 195 | # clean all 196 | # ------------------------------------------------------------------------------ 197 | .PHONY: clean 198 | clean: 199 | @-$(ECHO) +++ cleaning all 200 | @-$(RM) -rf $(PATH_BIN) 2> $(PATH_NUL) 201 | @-$(RM) -rf $(PATH_TMP) 2> $(PATH_NUL) 202 | @-$(MKDIR) -p $(PATH_BIN) 203 | @-$(MKDIR) -p $(PATH_OBJ) 204 | @-$(MKDIR) -p $(PATH_ERR) 205 | @-$(MKDIR) -p $(PATH_LST) 206 | @-$(MKDIR) -p $(PATH_COV) 207 | 208 | 209 | # ------------------------------------------------------------------------------ 210 | # print the GNUmake version and the compiler version 211 | # ------------------------------------------------------------------------------ 212 | .PHONY: version 213 | version: 214 | # Print the GNU make version and the compiler version 215 | @$(ECHO) GNUmake version: 216 | @$(MAKE) --version 217 | @$(ECHO) GCC version: 218 | @$(CL) -v 219 | 220 | 221 | # ------------------------------------------------------------------------------ 222 | # Rules 223 | # ------------------------------------------------------------------------------ 224 | 225 | # ------------------------------------------------------------------------------ 226 | # Link/locate application 227 | # ------------------------------------------------------------------------------ 228 | $(TRG) : $(FILES_O) 229 | @-$(ECHO) +++ linkink application to generate: $(TRG) 230 | @-$(CL) $(LFLAGS) -L. -lc $(PATH_OBJ)/*.o -Wl,-Map,$(TRG).map -o $(TRG) 231 | # profiling 232 | @-$(CL) $(LFLAGS) -L. -lc $(PATH_COV)/*.o --coverage -o $(PATH_COV)/$(APP) 233 | 234 | 235 | # ------------------------------------------------------------------------------ 236 | # parse the object files to obtain symbol information, and create a size summary 237 | # ------------------------------------------------------------------------------ 238 | $(TRG)_nm.txt : $(TRG) 239 | @-$(ECHO) +++ parsing symbols with nm to generate: $(TRG)_nm.txt 240 | @-$(NM) --numeric-sort --print-size $(TRG) > $(TRG)_nm.txt 241 | @-$(ECHO) +++ demangling symbols with c++filt to generate: $(TRG)_cppfilt.txt 242 | @-$(NM) --numeric-sort --print-size $(TRG) | $(CPPFILT) > $(TRG)_cppfilt.txt 243 | @-$(ECHO) +++ creating size summary table with size to generate: $(TRG)_size.txt 244 | @-$(SIZE) -A -t $(TRG) > $(TRG)_size.txt 245 | 246 | 247 | %.o : %.cpp 248 | @$(ECHO) +++ compile: $< 249 | # Compile the source file 250 | # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window 251 | # ...and Create an assembly listing using objdump 252 | # ...and Generate a dependency file (using the -MM flag) 253 | @-$(CL) $(CPPFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre 254 | @-$(CL) $(CPPFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err 255 | @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err 256 | @-$(OBJDUMP) --disassemble --line-numbers -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst 257 | @-$(CL) $(CPPFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d 258 | # profiling 259 | @-$(CL) $(CPPFLAGS) -O0 --coverage $< -c -o $(PATH_COV)/$(basename $(@F)).o 2> $(PATH_NUL) 260 | 261 | %.o : %.c 262 | @$(ECHO) +++ compile: $< 263 | # Compile the source file 264 | # ...and Reformat (using sed) any possible error/warning messages for the VisualStudio(R) output window 265 | # ...and Create an assembly listing using objdump 266 | # ...and Generate a dependency file (using the -MM flag) 267 | @-$(CL) $(CFLAGS) $< -E -o $(PATH_PRE)/$(basename $(@F)).pre 268 | @-$(CC) $(CFLAGS) $< -c -o $(PATH_OBJ)/$(basename $(@F)).o 2> $(PATH_ERR)/$(basename $(@F)).err 269 | @-$(SED) -e 's|.h:\([0-9]*\),|.h(\1) :|' -e 's|:\([0-9]*\):|(\1) :|' $(PATH_ERR)/$(basename $(@F)).err 270 | @-$(OBJDUMP) -S $(PATH_OBJ)/$(basename $(@F)).o > $(PATH_LST)/$(basename $(@F)).lst 271 | @-$(CC) $(CFLAGS) $< -MM > $(PATH_OBJ)/$(basename $(@F)).d 272 | -------------------------------------------------------------------------------- /avl_array.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // \author (c) Marco Paland (info@paland.com) 3 | // 2017-2020, paland consult, Hannover, Germany 4 | // 5 | // \license The MIT License (MIT) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | // \brief avl_array class 26 | // This is an AVL tree implementation using an array as data structure. 27 | // avl_array combines the insert/delete and find advantages (log n) of an AVL tree 28 | // with a static allocated arrays and minimal storage overhead. 29 | // If memory is critical the 'Fast' template parameter can be set to false which 30 | // removes the parent member of every node. This saves sizeof(size_type) * Size bytes, 31 | // but slowes down the insert and delete operation by factor 10 due to 'parent search'. 32 | // The find opeartion is not affected cause finding doesn't need a parent. 33 | // 34 | // usage: 35 | // #include "avl_array.h" 36 | // avl_array avl; 37 | // avl.insert(1, 1); 38 | // 39 | /////////////////////////////////////////////////////////////////////////////// 40 | 41 | #ifndef _AVL_ARRAY_H_ 42 | #define _AVL_ARRAY_H_ 43 | 44 | #include 45 | 46 | 47 | /** 48 | * \param Key The key type. The type (class) must provide a 'less than' and 'equal to' operator 49 | * \param T The Data type 50 | * \param size_type Container size type 51 | * \param Size Container size 52 | * \param Fast If true every node stores an extra parent index. This increases memory but speed up insert/erase by factor 10 53 | */ 54 | template 55 | class avl_array 56 | { 57 | // child index pointer class 58 | typedef struct tag_child_type { 59 | size_type left; 60 | size_type right; 61 | } child_type; 62 | 63 | // node storage, due to possible structure packing effects, single arrays are used instead of a 'node' structure 64 | Key key_[Size]; // node key 65 | T val_[Size]; // node value 66 | std::int8_t balance_[Size]; // subtree balance 67 | child_type child_[Size]; // node childs 68 | size_type size_; // actual size 69 | size_type root_; // root node 70 | size_type parent_[Fast ? Size : 1]; // node parent, use one element if not needed (zero sized array is not allowed) 71 | 72 | // invalid index (like 'nullptr' in a pointer implementation) 73 | static const size_type INVALID_IDX = Size; 74 | 75 | // iterator class 76 | typedef class tag_avl_array_iterator 77 | { 78 | avl_array* instance_; // array instance 79 | size_type idx_; // actual node 80 | 81 | friend avl_array; // avl_array may access index pointer 82 | 83 | public: 84 | // ctor 85 | tag_avl_array_iterator(avl_array* instance = nullptr, size_type idx = 0U) 86 | : instance_(instance) 87 | , idx_(idx) 88 | { } 89 | 90 | inline tag_avl_array_iterator& operator=(const tag_avl_array_iterator& other) 91 | { 92 | instance_ = other.instance_; 93 | idx_ = other.idx_; 94 | return *this; 95 | } 96 | 97 | inline bool operator==(const tag_avl_array_iterator& rhs) const 98 | { return idx_ == rhs.idx_; } 99 | 100 | inline bool operator!=(const tag_avl_array_iterator& rhs) const 101 | { return !(*this == rhs); } 102 | 103 | // dereference - access value 104 | inline T& operator*() const 105 | { return val(); } 106 | 107 | // access value 108 | inline T& val() const 109 | { return instance_->val_[idx_]; } 110 | 111 | // access key 112 | inline Key& key() const 113 | { return instance_->key_[idx_]; } 114 | 115 | // preincrement 116 | tag_avl_array_iterator& operator++() 117 | { 118 | // end reached? 119 | if (idx_ >= Size) { 120 | return *this; 121 | } 122 | // take left most child of right child, if not existent, take parent 123 | size_type i = instance_->child_[idx_].right; 124 | if (i != instance_->INVALID_IDX) { 125 | // successor is the furthest left node of right subtree 126 | for (; i != instance_->INVALID_IDX; i = instance_->child_[i].left) { 127 | idx_ = i; 128 | } 129 | } 130 | else { 131 | // have already processed the left subtree, and 132 | // there is no right subtree. move up the tree, 133 | // looking for a parent for which nodePtr is a left child, 134 | // stopping if the parent becomes NULL. a non-NULL parent 135 | // is the successor. if parent is NULL, the original node 136 | // was the last node inorder, and its successor 137 | // is the end of the list 138 | i = instance_->get_parent(idx_); 139 | while ((i != instance_->INVALID_IDX) && (idx_ == instance_->child_[i].right)) { 140 | idx_ = i; 141 | i = instance_->get_parent(idx_); 142 | } 143 | idx_ = i; 144 | } 145 | return *this; 146 | } 147 | 148 | // postincrement 149 | inline tag_avl_array_iterator operator++(int) 150 | { 151 | tag_avl_array_iterator _copy = *this; 152 | ++(*this); 153 | return _copy; 154 | } 155 | } avl_array_iterator; 156 | 157 | 158 | public: 159 | 160 | typedef T value_type; 161 | typedef T* pointer; 162 | typedef const T* const_pointer; 163 | typedef T& reference; 164 | typedef const T& const_reference; 165 | typedef Key key_type; 166 | typedef avl_array_iterator iterator; 167 | 168 | 169 | // ctor 170 | avl_array() 171 | : size_(0U) 172 | , root_(Size) 173 | { } 174 | 175 | 176 | // iterators 177 | inline iterator begin() 178 | { 179 | size_type i = INVALID_IDX; 180 | if (root_ != INVALID_IDX) { 181 | // find smallest element, it's the farthest node left from root 182 | for (i = root_; child_[i].left != INVALID_IDX; i = child_[i].left); 183 | } 184 | return iterator(this, i); 185 | } 186 | 187 | inline iterator end() 188 | { return iterator(this, INVALID_IDX); } 189 | 190 | 191 | // capacity 192 | inline size_type size() const 193 | { return size_; } 194 | 195 | inline bool empty() const 196 | { return size_ == static_cast(0); } 197 | 198 | inline size_type max_size() const 199 | { return Size; } 200 | 201 | 202 | /** 203 | * Clear the container 204 | */ 205 | inline void clear() 206 | { 207 | size_ = 0U; 208 | root_ = INVALID_IDX; 209 | } 210 | 211 | 212 | /** 213 | * Insert or update an element 214 | * \param key The key to insert. If the key already exists, it is updated 215 | * \param val Value to insert or update 216 | * \return True if the key was successfully inserted or updated, false if container is full 217 | */ 218 | bool insert(const key_type& key, const value_type& val) 219 | { 220 | if (root_ == INVALID_IDX) { 221 | key_[size_] = key; 222 | val_[size_] = val; 223 | balance_[size_] = 0; 224 | child_[size_] = { INVALID_IDX, INVALID_IDX }; 225 | set_parent(size_, INVALID_IDX); 226 | root_ = size_++; 227 | return true; 228 | } 229 | 230 | for (size_type i = root_; i != INVALID_IDX; i = (key < key_[i]) ? child_[i].left : child_[i].right) { 231 | if (key < key_[i]) { 232 | if (child_[i].left == INVALID_IDX) { 233 | if (size_ >= max_size()) { 234 | // container is full 235 | return false; 236 | } 237 | key_[size_] = key; 238 | val_[size_] = val; 239 | balance_[size_] = 0; 240 | child_[size_] = { INVALID_IDX, INVALID_IDX }; 241 | set_parent(size_, i); 242 | child_[i].left = size_++; 243 | insert_balance(i, 1); 244 | return true; 245 | } 246 | } 247 | else if (key_[i] == key) { 248 | // found same key, update node 249 | val_[i] = val; 250 | return true; 251 | } 252 | else { 253 | if (child_[i].right == INVALID_IDX) { 254 | if (size_ >= max_size()) { 255 | // container is full 256 | return false; 257 | } 258 | key_[size_] = key; 259 | val_[size_] = val; 260 | balance_[size_] = 0; 261 | child_[size_] = { INVALID_IDX, INVALID_IDX }; 262 | set_parent(size_, i); 263 | child_[i].right = size_++; 264 | insert_balance(i, -1); 265 | return true; 266 | } 267 | } 268 | } 269 | // node doesn't fit (should not happen) - discard it anyway 270 | return false; 271 | } 272 | 273 | 274 | /** 275 | * Find an element 276 | * \param key The key to find 277 | * \param val If key is found, the value of the element is set 278 | * \return True if key was found 279 | */ 280 | inline bool find(const key_type& key, value_type& val) const 281 | { 282 | for (size_type i = root_; i != INVALID_IDX;) { 283 | if (key < key_[i]) { 284 | i = child_[i].left; 285 | } 286 | else if (key == key_[i]) { 287 | // found key 288 | val = val_[i]; 289 | return true; 290 | } 291 | else { 292 | i = child_[i].right; 293 | } 294 | } 295 | // key not found 296 | return false; 297 | } 298 | 299 | 300 | /** 301 | * Find an element and return an iterator as result 302 | * \param key The key to find 303 | * \return Iterator if key was found, else end() is returned 304 | */ 305 | inline iterator find(const key_type& key) 306 | { 307 | for (size_type i = root_; i != INVALID_IDX;) { 308 | if (key < key_[i]) { 309 | i = child_[i].left; 310 | } else if (key == key_[i]) { 311 | // found key 312 | return iterator(this, i); 313 | } 314 | else { 315 | i = child_[i].right; 316 | } 317 | } 318 | // key not found, return end() iterator 319 | return end(); 320 | } 321 | 322 | 323 | /** 324 | * Count elements with a specific key 325 | * Searches the container for elements with a key equivalent to key and returns the number of matches. 326 | * Because all elements are unique, the function can only return 1 (if the element is found) or zero (otherwise). 327 | * \param key The key to find/count 328 | * \return 0 if key was not found, 1 if key was found 329 | */ 330 | inline size_type count(const key_type& key) 331 | { 332 | return find(key) != end() ? 1U : 0U; 333 | } 334 | 335 | 336 | /** 337 | * Remove element by key 338 | * \param key The key of the element to remove 339 | * \return True if the element ws removed, false if key was not found 340 | */ 341 | inline bool erase(const key_type& key) 342 | { 343 | return erase(find(key)); 344 | } 345 | 346 | 347 | /** 348 | * Remove element by iterator position 349 | * THIS ERASE OPERATION INVALIDATES ALL ITERATORS! 350 | * \param position The iterator position of the element to remove 351 | * \return True if the element was successfully removed, false if error 352 | */ 353 | bool erase(iterator position) 354 | { 355 | if (empty() || (position == end())) { 356 | return false; 357 | } 358 | 359 | const size_type node = position.idx_; 360 | const size_type left = child_[node].left; 361 | const size_type right = child_[node].right; 362 | 363 | if (left == INVALID_IDX) { 364 | if (right == INVALID_IDX) { 365 | const size_type parent = get_parent(node); 366 | if (parent != INVALID_IDX) { 367 | if (child_[parent].left == node) { 368 | child_[parent].left = INVALID_IDX; 369 | delete_balance(parent, -1); 370 | } 371 | else { 372 | child_[parent].right = INVALID_IDX; 373 | delete_balance(parent, 1); 374 | } 375 | } 376 | else { 377 | root_ = INVALID_IDX; 378 | } 379 | } 380 | else { 381 | const size_type parent = get_parent(node); 382 | if (parent != INVALID_IDX) { 383 | child_[parent].left == node ? child_[parent].left = right : child_[parent].right = right; 384 | } 385 | else { 386 | root_ = right; 387 | } 388 | set_parent(right, parent); 389 | delete_balance(right, 0); 390 | } 391 | } 392 | else if (right == INVALID_IDX) { 393 | const size_type parent = get_parent(node); 394 | if (parent != INVALID_IDX) { 395 | child_[parent].left == node ? child_[parent].left = left : child_[parent].right = left; 396 | } 397 | else { 398 | root_ = left; 399 | } 400 | set_parent(left, parent); 401 | delete_balance(left, 0); 402 | } 403 | else { 404 | size_type successor = right; 405 | if (child_[successor].left == INVALID_IDX) { 406 | const size_type parent = get_parent(node); 407 | child_[successor].left = left; 408 | balance_[successor] = balance_[node]; 409 | set_parent(successor, parent); 410 | set_parent(left, successor); 411 | 412 | if (node == root_) { 413 | root_ = successor; 414 | } 415 | else { 416 | if (child_[parent].left == node) { 417 | child_[parent].left = successor; 418 | } 419 | else { 420 | child_[parent].right = successor; 421 | } 422 | } 423 | delete_balance(successor, 1); 424 | } 425 | else { 426 | while (child_[successor].left != INVALID_IDX) { 427 | successor = child_[successor].left; 428 | } 429 | 430 | const size_type parent = get_parent(node); 431 | const size_type successor_parent = get_parent(successor); 432 | const size_type successor_right = child_[successor].right; 433 | 434 | if (child_[successor_parent].left == successor) { 435 | child_[successor_parent].left = successor_right; 436 | } 437 | else { 438 | child_[successor_parent].right = successor_right; 439 | } 440 | 441 | set_parent(successor_right, successor_parent); 442 | set_parent(successor, parent); 443 | set_parent(right, successor); 444 | set_parent(left, successor); 445 | child_[successor].left = left; 446 | child_[successor].right = right; 447 | balance_[successor] = balance_[node]; 448 | 449 | if (node == root_) { 450 | root_ = successor; 451 | } 452 | else { 453 | if (child_[parent].left == node) { 454 | child_[parent].left = successor; 455 | } 456 | else { 457 | child_[parent].right = successor; 458 | } 459 | } 460 | delete_balance(successor_parent, -1); 461 | } 462 | } 463 | size_--; 464 | 465 | // relocate the node at the end to the deleted node, if it's not the deleted one 466 | if (node != size_) { 467 | size_type parent = INVALID_IDX; 468 | if (root_ == size_) { 469 | root_ = node; 470 | } 471 | else { 472 | parent = get_parent(size_); 473 | child_[parent].left == size_ ? child_[parent].left = node : child_[parent].right = node; 474 | } 475 | 476 | // correct childs parent 477 | set_parent(child_[size_].left, node); 478 | set_parent(child_[size_].right, node); 479 | 480 | // move content 481 | key_[node] = key_[size_]; 482 | val_[node] = val_[size_]; 483 | balance_[node] = balance_[size_]; 484 | child_[node] = child_[size_]; 485 | set_parent(node, parent); 486 | } 487 | 488 | return true; 489 | } 490 | 491 | 492 | /** 493 | * Integrity (self) check 494 | * \return True if the tree intergity is correct, false if error (should not happen normally) 495 | */ 496 | bool check() const 497 | { 498 | // check root 499 | if (empty() && (root_ != INVALID_IDX)) { 500 | // invalid root 501 | return false; 502 | } 503 | if (size() && root_ >= size()) { 504 | // root out of bounds 505 | return false; 506 | } 507 | 508 | // check tree 509 | for (size_type i = 0U; i < size(); ++i) 510 | { 511 | if ((child_[i].left != INVALID_IDX) && (!(key_[child_[i].left] < key_[i]) || (key_[child_[i].left] == key_[i]))) { 512 | // wrong key order to the left 513 | return false; 514 | } 515 | if ((child_[i].right != INVALID_IDX) && ((key_[child_[i].right] < key_[i]) || (key_[child_[i].right] == key_[i]))) { 516 | // wrong key order to the right 517 | return false; 518 | } 519 | const size_type parent = get_parent(i); 520 | if ((i != root_) && (parent == INVALID_IDX)) { 521 | // no parent 522 | return false; 523 | } 524 | if ((i == root_) && (parent != INVALID_IDX)) { 525 | // invalid root parent 526 | return false; 527 | } 528 | } 529 | // check passed 530 | return true; 531 | } 532 | 533 | 534 | ///////////////////////////////////////////////////////////////////////////// 535 | // Helper functions 536 | private: 537 | 538 | // find parent element 539 | inline size_type get_parent(size_type node) const 540 | { 541 | if (Fast) { 542 | return parent_[node]; 543 | } 544 | else { 545 | const Key key_node = key_[node]; 546 | for (size_type i = root_; i != INVALID_IDX; i = (key_node < key_[i]) ? child_[i].left : child_[i].right) { 547 | if ((child_[i].left == node) || (child_[i].right == node)) { 548 | // found parent 549 | return i; 550 | } 551 | } 552 | // parent not found 553 | return INVALID_IDX; 554 | } 555 | } 556 | 557 | 558 | // set parent element (only in Fast version) 559 | inline void set_parent(size_type node, size_type parent) 560 | { 561 | if (Fast) { 562 | if (node != INVALID_IDX) { 563 | parent_[node] = parent; 564 | } 565 | } 566 | } 567 | 568 | 569 | void insert_balance(size_type node, std::int8_t balance) 570 | { 571 | while (node != INVALID_IDX) { 572 | balance = (balance_[node] += balance); 573 | 574 | if (balance == 0) { 575 | return; 576 | } 577 | else if (balance == 2) { 578 | if (balance_[child_[node].left] == 1) { 579 | rotate_right(node); 580 | } 581 | else { 582 | rotate_left_right(node); 583 | } 584 | return; 585 | } 586 | else if (balance == -2) { 587 | if (balance_[child_[node].right] == -1) { 588 | rotate_left(node); 589 | } 590 | else { 591 | rotate_right_left(node); 592 | } 593 | return; 594 | } 595 | 596 | const size_type parent = get_parent(node); 597 | if (parent != INVALID_IDX) { 598 | balance = child_[parent].left == node ? 1 : -1; 599 | } 600 | node = parent; 601 | } 602 | } 603 | 604 | 605 | void delete_balance(size_type node, std::int8_t balance) 606 | { 607 | while (node != INVALID_IDX) { 608 | balance = (balance_[node] += balance); 609 | 610 | if (balance == -2) { 611 | if (balance_[child_[node].right] <= 0) { 612 | node = rotate_left(node); 613 | if (balance_[node] == 1) { 614 | return; 615 | } 616 | } 617 | else { 618 | node = rotate_right_left(node); 619 | } 620 | } 621 | else if (balance == 2) { 622 | if (balance_[child_[node].left] >= 0) { 623 | node = rotate_right(node); 624 | if (balance_[node] == -1) { 625 | return; 626 | } 627 | } 628 | else { 629 | node = rotate_left_right(node); 630 | } 631 | } 632 | else if (balance != 0) { 633 | return; 634 | } 635 | 636 | if (node != INVALID_IDX) { 637 | const size_type parent = get_parent(node); 638 | if (parent != INVALID_IDX) { 639 | balance = child_[parent].left == node ? -1 : 1; 640 | } 641 | node = parent; 642 | } 643 | } 644 | } 645 | 646 | 647 | size_type rotate_left(size_type node) 648 | { 649 | const size_type right = child_[node].right; 650 | const size_type right_left = child_[right].left; 651 | const size_type parent = get_parent(node); 652 | 653 | set_parent(right, parent); 654 | set_parent(node, right); 655 | set_parent(right_left, node); 656 | child_[right].left = node; 657 | child_[node].right = right_left; 658 | 659 | if (node == root_) { 660 | root_ = right; 661 | } 662 | else if (child_[parent].right == node) { 663 | child_[parent].right = right; 664 | } 665 | else { 666 | child_[parent].left = right; 667 | } 668 | 669 | balance_[right]++; 670 | balance_[node] = -balance_[right]; 671 | 672 | return right; 673 | } 674 | 675 | 676 | size_type rotate_right(size_type node) 677 | { 678 | const size_type left = child_[node].left; 679 | const size_type left_right = child_[left].right; 680 | const size_type parent = get_parent(node); 681 | 682 | set_parent(left, parent); 683 | set_parent(node, left); 684 | set_parent(left_right, node); 685 | child_[left].right = node; 686 | child_[node].left = left_right; 687 | 688 | if (node == root_) { 689 | root_ = left; 690 | } 691 | else if (child_[parent].left == node) { 692 | child_[parent].left = left; 693 | } 694 | else { 695 | child_[parent].right = left; 696 | } 697 | 698 | balance_[left]--; 699 | balance_[node] = -balance_[left]; 700 | 701 | return left; 702 | } 703 | 704 | 705 | size_type rotate_left_right(size_type node) 706 | { 707 | const size_type left = child_[node].left; 708 | const size_type left_right = child_[left].right; 709 | const size_type left_right_right = child_[left_right].right; 710 | const size_type left_right_left = child_[left_right].left; 711 | const size_type parent = get_parent(node); 712 | 713 | set_parent(left_right, parent); 714 | set_parent(left, left_right); 715 | set_parent(node, left_right); 716 | set_parent(left_right_right, node); 717 | set_parent(left_right_left, left); 718 | child_[node].left = left_right_right; 719 | child_[left].right = left_right_left; 720 | child_[left_right].left = left; 721 | child_[left_right].right = node; 722 | 723 | if (node == root_) { 724 | root_ = left_right; 725 | } 726 | else if (child_[parent].left == node) { 727 | child_[parent].left = left_right; 728 | } 729 | else { 730 | child_[parent].right = left_right; 731 | } 732 | 733 | if (balance_[left_right] == 0) { 734 | balance_[node] = 0; 735 | balance_[left] = 0; 736 | } 737 | else if (balance_[left_right] == -1) { 738 | balance_[node] = 0; 739 | balance_[left] = 1; 740 | } 741 | else { 742 | balance_[node] = -1; 743 | balance_[left] = 0; 744 | } 745 | balance_[left_right] = 0; 746 | 747 | return left_right; 748 | } 749 | 750 | 751 | size_type rotate_right_left(size_type node) 752 | { 753 | const size_type right = child_[node].right; 754 | const size_type right_left = child_[right].left; 755 | const size_type right_left_left = child_[right_left].left; 756 | const size_type right_left_right = child_[right_left].right; 757 | const size_type parent = get_parent(node); 758 | 759 | set_parent(right_left, parent); 760 | set_parent(right, right_left); 761 | set_parent(node, right_left); 762 | set_parent(right_left_left, node); 763 | set_parent(right_left_right, right); 764 | child_[node].right = right_left_left; 765 | child_[right].left = right_left_right; 766 | child_[right_left].right = right; 767 | child_[right_left].left = node; 768 | 769 | if (node == root_) { 770 | root_ = right_left; 771 | } 772 | else if (child_[parent].right == node) { 773 | child_[parent].right = right_left; 774 | } 775 | else { 776 | child_[parent].left = right_left; 777 | } 778 | 779 | if (balance_[right_left] == 0) { 780 | balance_[node] = 0; 781 | balance_[right] = 0; 782 | } 783 | else if (balance_[right_left] == 1) { 784 | balance_[node] = 0; 785 | balance_[right] = -1; 786 | } 787 | else { 788 | balance_[node] = 1; 789 | balance_[right] = 0; 790 | } 791 | balance_[right_left] = 0; 792 | 793 | return right_left; 794 | } 795 | }; 796 | 797 | #endif // _AVL_ARRAY_H_ 798 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AVL Array container class 2 | 3 | [![Build Status](https://travis-ci.org/mpaland/avl_array.svg?branch=master)](https://travis-ci.org/mpaland/avl_array) 4 | [![Coveralls Status](https://coveralls.io/repos/github/mpaland/avl_array/badge.svg?branch=master)](https://coveralls.io/github/mpaland/avl_array?branch=master) 5 | [![Coverity Status](https://img.shields.io/coverity/scan/14061.svg)](https://scan.coverity.com/projects/mpaland-avl_array) 6 | [![Github Issues](https://img.shields.io/github/issues/mpaland/avl_array.svg)](http://github.com/mpaland/avl_array/issues) 7 | [![Github Releases](https://img.shields.io/github/release/mpaland/avl_array.svg)](https://github.com/mpaland/avl_array/releases) 8 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mpaland/avl_array/master/LICENSE) 9 | 10 | **avl_array** is a templated C++ (STL map like) container class that stores key-value data organzied as AVL-tree in a **fixed size** array. 11 | 12 | For an embedded project I needed a high-performance key-value store in static memory with the fastest key retrieval time I could get. 13 | Normally a `std::map` container with a static custom allocator is taken, but `std::map` pulls a lot of unwanted stuff and dependencies into the project while its performance is rather average. 14 | That being said, motivation of this container is to insert, update, delete and find random key-value elements in *static allocated* memory with highest performance and in a minimum of time. 15 | It might also be the base class for an associative array container. 16 | 17 | ## Highligths and design goals 18 | - `std::map` like templated container class with increment iterator support 19 | - Ultra fast, maximum performance, minimum footprint and **no dependencies** (compared to `std::map`) 20 | - Static allocated memory (as template parameter) 21 | - NO recursive calls 22 | - Small memory overhead (arround 5 byte per node in slow-mode) 23 | - VERY clean and stable C++ code, LINT and L4 warning free, automotive ready 24 | - Optimized for embedded system usage 25 | - Very easy to use, just include "avl_array.h" 26 | - Extensive test suite 27 | - Doxygen commented code 28 | - MIT license 29 | 30 | 31 | ### Comparison of different access containers 32 | 33 | | Container | Operation | Worst Case Cost | add. memory overhead | 34 | |-----------|-----------|:---------------:|----------------------| 35 | | Unsorted Array | insert / delete | O(n) | none | 36 | | | find / update | O(n) | | 37 | | Sorted Array | insert / delete | O(n log n) (via Heapsort) | none | 38 | | | find / update | O(log n) | | 39 | | **AVL Array** | insert /delete | O(log n) | min. 5 byte / element 40 | | | find / update | O(log n) | | 41 | 42 | 43 | ## History 44 | In computer science, an [AVL tree](https://en.wikipedia.org/wiki/AVL_tree) is a self-balancing binary search tree with a primary rule: the height of two childrens's subtrees of any node differ at most by one. At no time they differ by more than one because rebalancing is done to ensure this rule. 45 | Lookup, insertion and deletion take O(log n) time in both the average and worst case, where n is the number of nodes in the tree prior to the operation. 46 | Insertions and deletions may require the tree to be rebalanced by one or more tree rotations. 47 | Other trees like Red-black trees may not guarantee O(log n) in worst case search. 48 | 49 | There are a lot of AVL implementations around the internet which are pointer based, so that every node element has to store additional pointers to its parent, left and right child nodes. 50 | Advantage of this method is slightly fast rebalancing/rotation because only some pointers need to be exchanged and the node values remain in place. 51 | Disadvantage is the storage overhead of three additional pointers for each node. If memory is an issue (like in small embedded systems), this might be a problem. 52 | 53 | The alternative idea is to implement the AVL tree as pure array without any pointers, using an over 400 years old implementation technique called Eytzinger's method ("Ahnentafel" in German) which allows to represent a complete binary tree as an array. This is done by laying out the nodes of the tree in breadth-first order in the array. Having this said, the root is stored at position 0, the root's left child is stored at position 1, the root's right child at position 2, the left child of the left child of the root is stored at position 3 and so on. 54 | Related nodes are accessed by the following calculation: 55 | 56 | | Element | Access | 57 | |---------|-------------| 58 | | parent | (i - 1) / 2 | 59 | | left | 2 * i + 1 | 60 | | right | 2 * i + 2 | 61 | 62 | You can find a basic implementation of this method from @willemt [here](https://github.com/willemt/array-avl-tree), but it seems to have several bugs and my test cases did not pass at all. 63 | So I implemented an own class which looked very promising... regarding the memory layout. 64 | BUT: Problem is the movement of nodes in balancing operations after each insert or delete. With increasing tree size a lot of nodes (values) need to be moved around in the array. 65 | Recursive function calls (which can be eliminated of course) and out of tree insertions, which occur when the last tree row is used, are another problem. 66 | After some tests it became very clear that this method is weak and complicated when inserting new nodes in a big tree. 67 | Goal is to leave all the nodes in place once they are inserted. This can only be achieved by - you already know - changing pointer/index values. 68 | My implementation of the AVL tree class here is inspired by a [blog of Keith Woods](https://bitlush.com/blog/efficient-avl-tree-in-c-sharp) and his high speed [implementation](https://github.com/bitlush/avl-tree-c-sharp) in C#. But instead of dynamic memory it uses a fixed array to store nodes and just two additional child indexes using the template given index type. 69 | So there's a storage overhead of (2 * sizeof(size_type) + balance_byte) * Size, e.g. (2 * 2 byte + 1) * 1023 = 5115 bytes for a 1023 node tree. 70 | 71 | 72 | ## Usage 73 | Using the AVL array container is pretty simple. Most functions are very similar to the standard `std::map` container. 74 | 75 | ```c++ 76 | #include 77 | 78 | // create a 2048 node tree with as key and value types and as size type in 'Fast' mode 79 | avl_array avl; 80 | 81 | // insert 82 | avl.insert(1, 1); // set value of key 1 to 1 83 | avl.insert(2, 2); // set value of key 2 to 2 84 | avl.insert(3, 3); // set value of key 3 to 3 85 | 86 | // update 87 | avl.insert(2, 4); // update value of key 2 to 4 88 | 89 | // find 90 | int val = *avl.find(2); // as iterator (returns 4) 91 | bool res = avl.find(1, val); // as data type (returns 1) 92 | 93 | // using an iterator to access the values of the according keys in ascending key order 94 | // output is: 1 4 3 95 | for (auto it = avl.begin(); it != avl.end(); ++it) { 96 | std::cout << *it << " "; 97 | } 98 | 99 | // erase 100 | avl.erase(2); // erase key 2 101 | ``` 102 | 103 | 104 | ### Fast mode 105 | This class has two compile time selectable modes as template parameter `Fast` (default is `true`). 106 | 107 | | Fast | Description | 108 | |------|-------------| 109 | | true | Usage of an addional parent index. This consumes Size * sizeof(size_type) bytes of additional memory but increases the speed of insert and delete operations by factor 10. | 110 | | false | A parent node search algorithm is used. This is slower for insert and delete operations, but the internal parent index is omitted. Use this mode if memory is critical and insert/delete performance is not a big issue. | 111 | 112 | Search (find) speed is not affected by `Fast` and is always O(log n) fast. 113 | 114 | 115 | ## Caveats 116 | **The `erase()` function invalidates any iterators!** 117 | After erasing a node, an iterator must be initialized again (e.g. via the `begin()` or `find()` function). 118 | 119 | 120 | ## Test and run 121 | For testing just compile, build and run the test suite located in `test/test_suite.cpp`. This uses the [catch](https://github.com/philsquared/Catch) framework for unit-tests, which is auto-adding `main()`. 122 | 123 | 124 | ## Projects using avl_array 125 | - The [vic library](https://github.com/mpaland/vic) uses avl_array as sprite/background pixel buffer for fast sprite rendering. 126 | 127 | 128 | ## Contributing 129 | 1. Create an issue and describe your idea 130 | 2. [Fork it](https://github.com/mpaland/avl_array/fork) 131 | 3. Create your feature branch (`git checkout -b my-new-feature`) 132 | 4. Commit your changes (`git commit -am 'Add some feature'`) 133 | 5. Publish the branch (`git push origin my-new-feature`) 134 | 6. Create a new pull request 135 | 7. Profit! :white_check_mark: 136 | 8. Please report any issues. 137 | 138 | 139 | ## License 140 | avl_array is written under the [MIT license](http://www.opensource.org/licenses/MIT). 141 | -------------------------------------------------------------------------------- /test/test_suite.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // \author (c) Marco Paland (info@paland.com) 3 | // 2017, PALANDesign Hannover, Germany 4 | // 5 | // \license The MIT License (MIT) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | // \brief avl_array unit tests 26 | // 27 | /////////////////////////////////////////////////////////////////////////////// 28 | 29 | // use the 'catch' test framework 30 | #define CATCH_CONFIG_MAIN 31 | #include "catch.hpp" 32 | 33 | #include 34 | #include "../avl_array.h" 35 | 36 | 37 | 38 | TEST_CASE("Capacity", "[capacity]" ) { 39 | avl_array avl; 40 | REQUIRE(avl.empty()); 41 | REQUIRE(avl.size() == 0U); 42 | REQUIRE(avl.max_size() == 1024); 43 | avl.insert(1, 1); 44 | REQUIRE(!avl.empty()); 45 | REQUIRE(avl.size() == 1U); 46 | avl.insert(2, 2); 47 | REQUIRE(!avl.empty()); 48 | REQUIRE(avl.size() == 2U); 49 | avl.clear(); 50 | REQUIRE(avl.empty()); 51 | REQUIRE(avl.size() == 0U); 52 | avl.insert(1, 1); 53 | REQUIRE(!avl.empty()); 54 | REQUIRE(avl.size() == 1U); 55 | } 56 | 57 | 58 | TEST_CASE("Max capacity, size", "[capacity]" ) { 59 | avl_array avl; 60 | REQUIRE(avl.empty()); 61 | REQUIRE(avl.size() == 0U); 62 | REQUIRE(avl.max_size() == 1024U); 63 | for (int n = 1; n <= 1024; n++) { 64 | REQUIRE(avl.insert(n, n)); 65 | REQUIRE(avl.size() == n); 66 | REQUIRE(!avl.empty()); 67 | } 68 | REQUIRE(!avl.insert(1025, 1025)); 69 | REQUIRE(avl.size() == 1024U); 70 | } 71 | 72 | 73 | TEST_CASE("Forward insert", "[insert]" ) { 74 | avl_array avl; 75 | for (int n = 0; n < 1000; n++) { 76 | avl.insert(n, n); 77 | REQUIRE(avl.check()); 78 | } 79 | } 80 | 81 | 82 | TEST_CASE("Reverse insert", "[insert]" ) { 83 | avl_array avl; 84 | for (int n = 1022; n >= 0; n--) { 85 | avl.insert(n, n); 86 | REQUIRE(avl.check()); 87 | } 88 | } 89 | 90 | 91 | TEST_CASE("Equal insert", "[insert]" ) { 92 | avl_array avl; 93 | for (int n = 0; n < 10; n++) { 94 | avl.insert(5, 5); 95 | REQUIRE(avl.check()); 96 | } 97 | REQUIRE(avl.size() == 1U); 98 | } 99 | 100 | 101 | TEST_CASE("Random insert", "[insert]" ) { 102 | avl_array avl; 103 | srand(0U); 104 | for (int n = 0; n < 10000; n++) { 105 | const int r = rand(); 106 | REQUIRE(avl.insert(r, r)); 107 | REQUIRE(avl.check()); 108 | } 109 | avl.insert(1000, 1000); 110 | REQUIRE(avl.check()); 111 | 112 | avl.clear(); 113 | int ra[] = { 38, 7719, 21238, 2437, 8855, 11797, 8365, 32285, 10450, 30612, 5853, 28100, 1142, 281, 20537, 15921, 8945, 26285, 2997, 14680, 20976, 31891, 21655, 25906, 18457, 1323 }; 114 | for (size_t n = 0; n < sizeof(ra) / sizeof(ra[0]); n++) { 115 | REQUIRE(avl.insert(ra[n], ra[n])); 116 | REQUIRE(avl.check()); 117 | } 118 | for (size_t n = 0; n < sizeof(ra) / sizeof(ra[0]); n++) { 119 | REQUIRE(avl.count(ra[n]) == 1U); 120 | } 121 | REQUIRE(avl.count(1000) == 0U); 122 | } 123 | 124 | 125 | TEST_CASE("Random insert - slow mode", "[insert]" ) { 126 | avl_array avl; 127 | srand(0U); 128 | for (int n = 0; n < 10000; n++) { 129 | const int r = rand(); 130 | REQUIRE(avl.insert(r, r)); 131 | REQUIRE(avl.check()); 132 | } 133 | avl.insert(1000, 1000); 134 | REQUIRE(avl.check()); 135 | 136 | avl.clear(); 137 | int ra[] = { 38, 7719, 21238, 2437, 8855, 11797, 8365, 32285, 10450, 30612, 5853, 28100, 1142, 281, 20537, 15921, 8945, 26285, 2997, 14680, 20976, 31891, 21655, 25906, 18457, 1323 }; 138 | for (size_t n = 0; n < sizeof(ra) / sizeof(ra[0]); n++) { 139 | REQUIRE(avl.insert(ra[n], ra[n])); 140 | REQUIRE(avl.check()); 141 | } 142 | for (size_t n = 0; n < sizeof(ra) / sizeof(ra[0]); n++) { 143 | REQUIRE(avl.count(ra[n]) == 1U); 144 | } 145 | REQUIRE(avl.count(1000) == 0U); 146 | } 147 | 148 | 149 | TEST_CASE("Random erase", "[erase]" ) { 150 | avl_array avl; 151 | int arr[10000]; 152 | srand(0U); 153 | for (int n = 0; n < 10000; n++) { 154 | const int r = rand(); 155 | REQUIRE(avl.insert(r, r)); 156 | arr[n] = r; 157 | REQUIRE(avl.check()); 158 | } 159 | 160 | for (int n = 0; n < 10000; n++) { 161 | if (arr[n] != *avl.find(arr[n])) 162 | REQUIRE(arr[n] == *avl.find(arr[n])); 163 | } 164 | 165 | for (int n = 0; n < 10000; n++) { 166 | avl.erase(avl.find(arr[n])); 167 | REQUIRE(avl.check()); 168 | } 169 | REQUIRE(avl.empty()); 170 | } 171 | 172 | 173 | TEST_CASE("Random erase - slow mode", "[erase]" ) { 174 | avl_array avl; 175 | int arr[10000]; 176 | srand(0U); 177 | for (int n = 0; n < 10000; n++) { 178 | const int r = rand(); 179 | REQUIRE(avl.insert(r, r)); 180 | arr[n] = r; 181 | REQUIRE(avl.check()); 182 | } 183 | 184 | for (int n = 0; n < 10000; n++) { 185 | if (arr[n] != *avl.find(arr[n])) 186 | REQUIRE(arr[n] == *avl.find(arr[n])); 187 | } 188 | 189 | for (int n = 0; n < 10000; n++) { 190 | avl.erase(avl.find(arr[n])); 191 | REQUIRE(avl.check()); 192 | } 193 | REQUIRE(avl.empty()); 194 | } 195 | 196 | 197 | TEST_CASE("Erase key forward", "[erase]" ) { 198 | avl_array avl; 199 | for (int n = 0; n < 2048; n++) { 200 | REQUIRE(avl.insert(n, n)); 201 | REQUIRE(*avl.find(n) == n); 202 | } 203 | for (int n = 0; n < 2048; n++) { 204 | REQUIRE(avl.erase(n)); 205 | REQUIRE(avl.find(n) == avl.end()); 206 | REQUIRE(avl.check()); 207 | REQUIRE(2047 - avl.size() == n); 208 | if (avl.begin() != avl.end()) { 209 | REQUIRE(n + 1 == *avl.begin()); 210 | } 211 | int x = n + 1; 212 | for (auto it = avl.begin(); it != avl.end(); ++it) { 213 | REQUIRE(*it == x++); 214 | } 215 | } 216 | } 217 | 218 | 219 | TEST_CASE("Erase key reverse", "[erase]" ) { 220 | avl_array avl; 221 | for (int n = 0; n < 2048; n++) { 222 | REQUIRE(avl.insert(n, n)); 223 | REQUIRE(*avl.find(n) == n); 224 | } 225 | for (int n = 2047; n >= 0; n--) { 226 | REQUIRE(avl.erase(n)); 227 | REQUIRE(avl.find(n) == avl.end()); 228 | REQUIRE(avl.check()); 229 | REQUIRE(avl.size() == n); 230 | } 231 | } 232 | 233 | 234 | TEST_CASE("Erase iterator", "[erase]" ) { 235 | avl_array avl; 236 | REQUIRE(!avl.erase(avl.begin())); 237 | REQUIRE(!avl.erase(avl.end())); 238 | for (int n = 1; n < 2048; n++) { 239 | REQUIRE(avl.insert(n, n)); 240 | REQUIRE(*avl.find(n) == n); 241 | } 242 | REQUIRE(!avl.erase(avl.end())); 243 | for (int n = 1; n < 2048; n++) { 244 | REQUIRE(avl.erase(avl.find(n))); 245 | REQUIRE(avl.find(n) == avl.end()); 246 | REQUIRE(avl.check()); 247 | REQUIRE(2047 - avl.size() == n); 248 | } 249 | } 250 | 251 | 252 | TEST_CASE("Erase iterator 2", "[erase]" ) { 253 | avl_array avl; 254 | for (int n = 0; n < 2000; n++) { 255 | REQUIRE(avl.insert(n, n)); 256 | REQUIRE(*avl.find(n) == n); 257 | } 258 | avl_array::iterator it = avl.begin(); 259 | for (int n = 0; n < 2000; n++) { 260 | int k = ++it.key(); 261 | REQUIRE(avl.erase(it)); 262 | it = avl.find(k); 263 | REQUIRE(avl.check()); 264 | } 265 | REQUIRE(it == avl.end()); 266 | REQUIRE(avl.empty()); 267 | } 268 | 269 | 270 | TEST_CASE("Erase and insert", "[erase]" ) { 271 | avl_array avl; 272 | for (int n = 0; n < 2000; n++) { 273 | REQUIRE(avl.insert(n, n)); 274 | REQUIRE(*avl.find(n) == n); 275 | } 276 | for (int n = 1000; n >= 0; n--) { 277 | REQUIRE(avl.erase(avl.find(n))); 278 | REQUIRE(avl.size() == 999 + n); 279 | } 280 | int x = 1001; 281 | for (auto it = avl.begin(); it != avl.end(); ++it) { 282 | REQUIRE(*it == x++); 283 | } 284 | 285 | avl.clear(); 286 | for (int n = 0; n < 1000; n++) { 287 | REQUIRE(avl.insert(n, n)); 288 | REQUIRE(*avl.find(n) == n); 289 | } 290 | REQUIRE(avl.check()); 291 | for (int start = 0; start < 50000; start += 1000) { 292 | for (int n = start + 1000; n < start + 2000; n++) { 293 | REQUIRE(avl.insert(n, n)); 294 | REQUIRE(*avl.find(n) == n); 295 | } 296 | REQUIRE(avl.check()); 297 | for (int n = start; n < start + 1000; n++) { 298 | REQUIRE(avl.erase(avl.find(n))); 299 | REQUIRE(avl.check()); 300 | } 301 | // iterator check 302 | x = start + 1000; 303 | for (auto it = avl.begin(); it != avl.end(); ++it) { 304 | REQUIRE(*it == x++); 305 | } 306 | REQUIRE(x == start + 2000); 307 | } 308 | REQUIRE(avl.check()); 309 | } 310 | 311 | 312 | TEST_CASE("Iterator init", "[iterator]" ) { 313 | avl_array avl; 314 | avl_array::iterator it = avl.begin(); 315 | REQUIRE(it == avl.end()); 316 | } 317 | 318 | 319 | TEST_CASE("Iterator ++", "[iterator]" ) { 320 | avl_array avl; 321 | for (int n = 1; n < 2048; n++) { 322 | REQUIRE(avl.insert(n, n)); 323 | } 324 | int x = 1; 325 | for (auto it = avl.begin(); it != avl.end(); ++it) { 326 | REQUIRE(*it == x++); 327 | } 328 | 329 | avl.clear(); 330 | REQUIRE(avl.empty()); 331 | 332 | for (int n = 2000; n >= 0; n--) { 333 | REQUIRE(avl.insert(n, n)); 334 | REQUIRE(avl.check()); 335 | } 336 | x = 0; 337 | for (auto it = avl.begin(); it != avl.end(); it++) { 338 | REQUIRE(*it == x++); 339 | } 340 | x = 0; 341 | auto it = avl.begin(); 342 | for (int n = 0; n <= 2000; n++, ++it) { 343 | REQUIRE(*it == x++); 344 | } 345 | 346 | avl_array avl2; 347 | for (int k = 10, t = 1; k < 20; k++, t += 3) { 348 | REQUIRE(avl2.insert(k, t)); 349 | } 350 | avl_array::iterator it2; 351 | it2 = avl2.begin(); 352 | for (int k = 10, t = 1; k < 20; k++, t += 3, it2++) { 353 | REQUIRE(*it2 == t); 354 | } 355 | it2++; 356 | REQUIRE(it2 == avl2.end()); 357 | it2++; 358 | REQUIRE(it2 == avl2.end()); 359 | } 360 | 361 | 362 | TEST_CASE("Iterator assignment", "[iterator]" ) { 363 | avl_array avl; 364 | avl_array::iterator it; 365 | avl_array::iterator it2; 366 | 367 | avl.insert(1, 0xAA); 368 | 369 | it = avl.begin(); 370 | REQUIRE(*it == 0xAA); 371 | it2 = it; 372 | REQUIRE(*it2 == 0xAA); 373 | } 374 | 375 | 376 | TEST_CASE("Find (iterator)", "[find]" ) { 377 | avl_array avl; 378 | for (int n = 0; n < 2048; n++) { 379 | REQUIRE(avl.insert(n, n)); 380 | } 381 | REQUIRE(!avl.insert(2048, 2048)); 382 | 383 | for (int n = 0; n < 2048; n++) { 384 | REQUIRE(*avl.find(n) == n); 385 | } 386 | REQUIRE(avl.find(2048) == avl.end()); 387 | REQUIRE(avl.find(3000) == avl.end()); 388 | } 389 | 390 | 391 | TEST_CASE("Find (value)", "[find]" ) { 392 | avl_array avl; 393 | for (int n = 0; n < 2048; n++) { 394 | REQUIRE(avl.insert(n, n)); 395 | } 396 | REQUIRE(!avl.insert(2048, 2048)); 397 | 398 | int val; 399 | for (int n = 0; n < 2048; n++) { 400 | REQUIRE(avl.find(n, val)); 401 | } 402 | REQUIRE(!avl.find(2048, val)); 403 | REQUIRE(!avl.find(3000, val)); 404 | } 405 | 406 | 407 | TEST_CASE("Count", "[find]" ) { 408 | avl_array avl; 409 | for (int n = 0; n < 1023; n++) { 410 | avl.insert(n, n); 411 | } 412 | 413 | avl.insert(1000, 1000); 414 | avl.insert(1001, 1001); 415 | avl.insert(1001, 1001); 416 | 417 | for (int n = 0; n < 1023; n++) { 418 | REQUIRE(avl.count(n) == 1U); 419 | } 420 | for (int n = 1023; n < 2000; n++) { 421 | REQUIRE(avl.count(n) == 0U); 422 | } 423 | } 424 | 425 | 426 | TEST_CASE("Container size", "[size]" ) { 427 | { 428 | avl_array avl; 429 | avl.insert(1, 1); 430 | REQUIRE(avl.check()); 431 | avl.insert(2, 2); // not stored 432 | REQUIRE(avl.check()); 433 | REQUIRE(avl.size() == 1U); 434 | auto it = avl.begin(); 435 | REQUIRE(*it == 1); 436 | } 437 | { 438 | avl_array avl; 439 | avl.insert(1, 1); 440 | REQUIRE(avl.size() == 1U); 441 | avl.insert(2, 2); 442 | REQUIRE(avl.check()); 443 | avl.insert(3, 3); // not stored 444 | REQUIRE(avl.check()); 445 | REQUIRE(avl.size() == 2U); 446 | auto it = avl.begin(); 447 | REQUIRE(*it == 1); 448 | it++; 449 | REQUIRE(*it == 2); 450 | REQUIRE(avl.erase(1)); 451 | REQUIRE(avl.erase(2)); 452 | REQUIRE(avl.empty()); 453 | REQUIRE(avl.size() == 0U); 454 | } 455 | { 456 | avl_array avl; 457 | avl.insert(1, 1); 458 | avl.insert(2, 2); 459 | avl.insert(3, 3); 460 | REQUIRE(avl.check()); 461 | avl.insert(4, 4); 462 | REQUIRE(avl.check()); 463 | REQUIRE(avl.size() == 3U); 464 | auto it = avl.begin(); 465 | REQUIRE(*it == 1); 466 | it++; 467 | REQUIRE(*it == 2); 468 | it++; 469 | REQUIRE(*it == 3); 470 | } 471 | { 472 | avl_array avl; 473 | avl.insert(1, 1); 474 | avl.insert(2, 2); 475 | avl.insert(3, 3); 476 | avl.insert(4, 4); 477 | REQUIRE(avl.check()); 478 | avl.insert(5, 5); 479 | REQUIRE(avl.check()); 480 | REQUIRE(avl.size() == 4U); 481 | auto it = avl.begin(); 482 | REQUIRE(*it == 1); 483 | it++; 484 | REQUIRE(*it == 2); 485 | it++; 486 | REQUIRE(*it == 3); 487 | it++; 488 | REQUIRE(*it == 4); 489 | } 490 | { 491 | avl_array avl; 492 | avl.insert(1, 1); 493 | avl.insert(2, 2); 494 | avl.insert(3, 3); 495 | avl.insert(4, 4); 496 | avl.insert(5, 5); 497 | REQUIRE(avl.check()); 498 | avl.insert(6, 6); 499 | REQUIRE(avl.check()); 500 | REQUIRE(avl.size() == 5U); 501 | auto it = avl.begin(); 502 | REQUIRE(*it == 1); 503 | it++; 504 | REQUIRE(*it == 2); 505 | it++; 506 | REQUIRE(*it == 3); 507 | it++; 508 | REQUIRE(*it == 4); 509 | it++; 510 | REQUIRE(*it == 5); 511 | } 512 | } 513 | --------------------------------------------------------------------------------