├── oak.cpp ├── .travis.yml ├── LICENSE ├── demo.cc ├── README.md ├── tests.cxx └── oak.hpp /oak.cpp: -------------------------------------------------------------------------------- 1 | #include "oak.hpp" 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | 4 | compiler: 5 | - clang 6 | - gcc 7 | 8 | install: 9 | - wget --quiet -O - https://raw.githubusercontent.com/r-lyeh/depot/master/travis.pre.sh | bash -x 10 | 11 | script: 12 | - wget --quiet -O - https://raw.githubusercontent.com/r-lyeh/depot/master/travis.build.sh | bash -x 13 | - wget --quiet -O - https://raw.githubusercontent.com/r-lyeh/depot/master/travis.run.sh | bash -x 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /demo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "oak.hpp" 4 | 5 | int main() { 6 | oak::tree t; 7 | 8 | // read-write access [] operator (creates trees on demand) 9 | t["fruits"]["oranges"]; 10 | t["fruits"]["pineapples"] = 6; 11 | t["fruits"]["lemons"] = 12; 12 | t["animals"]["pigs"]["pink"] = 0; 13 | t["animals"]["pigs"]["guinea"] = 0; 14 | 15 | assert( 2 == t.size() ); 16 | assert( 3 == t["fruits"].size() ); 17 | 18 | // read-only access () operator (does not create tree on demand) 19 | t("fruits")("operator () should never create on demand"); 20 | assert( 3 == t("fruits").size() ); 21 | 22 | // print some subtrees 23 | for( auto &k : t("animals") ) { 24 | std::cout << ".key=" << k.first << "; .tree {\n" << k.second << "}" << std::endl; 25 | } 26 | 27 | // more printing 28 | std::cout << t << std::endl; 29 | std::cout << t.as_csv() << std::endl; 30 | 31 | std::cout << "All ok." << std::endl; 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | oak 2 | === 3 | 4 | - Oak is a lightweight tree container. Written in C++03. 5 | - Oak is simple. Loosely modeled after `std::map` interface with a few extras. 6 | - Oak is tiny. Header-only. 7 | - Oak is cross-platform. 8 | - Oak is stand-alone. No dependencies. 9 | - Oak is zlib/libpng licensed. 10 | 11 | ### API 12 | - Oak is loosely modeled after `std::map` interface with a few extras. 13 | - Use `[k]` to read/create subtrees on demand (standard `std::map` behavior). 14 | - Use `(k)` to read subtrees. Does not create subtrees on demand. 15 | - Use `.empty(k)` to check subtree existence. 16 | - Use `.get(v)/.set(v)` to get/set leaf values. 17 | - Use `.setdown()/.setup()` to set children/parent values. 18 | 19 | ### Sample 20 | ```c++ 21 | #include 22 | #include 23 | #include "oak.hpp" 24 | 25 | int main() { 26 | oak::tree t; 27 | 28 | // read-write access [] operator (creates trees on demand) 29 | t["fruits"]["oranges"]; 30 | t["fruits"]["pineapples"] = 6; 31 | t["fruits"]["lemons"] = 12; 32 | t["animals"]["pigs"]["pink"] = 0; 33 | t["animals"]["pigs"]["guinea"] = 0; 34 | 35 | assert( 2 == t.size() ); 36 | assert( 3 == t["fruits"].size() ); 37 | 38 | // read-only access () operator (does not create tree on demand) 39 | t("fruits")("operator () should never create on demand"); 40 | assert( 3 == t("fruits").size() ); 41 | 42 | // print some subtrees 43 | for( auto &k : t("animals") ) { 44 | std::cout << ".key=" << k.first << "; .tree {\n" << k.second << "}" << std::endl; 45 | } 46 | 47 | // more printing 48 | std::cout << t << std::endl; 49 | std::cout << t.as_csv() << std::endl; 50 | 51 | std::cout << "All ok." << std::endl; 52 | } 53 | ``` 54 | 55 | ### Possible output 56 | ```c++ 57 | .key=pigs; .tree { 58 | [2] guinea (0) 59 | [2] pink (0) 60 | } 61 | [2] animals (0) 62 | [1] pigs (0) 63 | [2] guinea (0) 64 | [2] pink (0) 65 | [2] fruits (0) 66 | [3] lemons (12) 67 | [3] oranges (0) 68 | [3] pineapples (6) 69 | 70 | /animals,0 71 | /animals/pigs,0 72 | /animals/pigs/guinea,0 73 | /animals/pigs/pink,0 74 | /fruits,0 75 | /fruits/lemons,12 76 | /fruits/oranges,0 77 | /fruits/pineapples,6 78 | 79 | All ok. 80 | ``` 81 | 82 | ## @todoc 83 | - rest of API, including `walk()` 84 | 85 | ## Changelog 86 | - v1.0.0 (2015/10/25): Semantic versioning adherence 87 | - v1.0.0 (2015/10/25): Fix csv() method when using non-string keys in map 88 | -------------------------------------------------------------------------------- /tests.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "oak.hpp" 4 | 5 | int main() 6 | { 7 | oak::tree t; 8 | 9 | t("fruits"); 10 | t("fruits")("oranges"); 11 | t("fruits")("lemons"); 12 | t("fruits")("grapes"); 13 | t("animals")("pigs")("guinea"); 14 | t("animals")("pigs")("thai"); 15 | t("animals")("wombats"); 16 | t("world"); 17 | t("work"); 18 | t("omg"); 19 | t("oh noes"); 20 | 21 | assert( !t.has("fruits") ); 22 | assert( !t("fruits") ); 23 | assert( t.size() == 0 ); 24 | 25 | t["fruits"]; 26 | t["fruits"]["oranges"]; 27 | t["fruits"]["lemons"]; 28 | t["fruits"]["grapes"]; 29 | t["animals"]["pigs"]["guinea"]; 30 | t["animals"]["pigs"]["thai"]; 31 | t["animals"]["wombats"]; 32 | t["omg"]; 33 | t["oh noes"]; 34 | 35 | assert( t.has("fruits") ); 36 | assert( t["fruits"] ); 37 | assert( t.size() == 4 ); 38 | 39 | t["animals"]["pigs"].set( 1 ); 40 | assert( t["animals"]["pigs"].get() == 1 ); 41 | std::cout << t << std::endl; 42 | t["animals"]["pigs"].setdown( 2 ); 43 | std::cout << t << std::endl; 44 | t["animals"]["pigs"].setup( 3 ); 45 | std::cout << t << std::endl; 46 | 47 | t.erase("omg"); 48 | assert( t.size() == 3 ); // t has 3 children 49 | 50 | assert( t("fruits") ); 51 | assert( t("fruits")("oranges") ); 52 | assert( !t("fruits")("watermelons") ); 53 | 54 | assert( t("fruits").size() == 3 ); 55 | assert( t("animals")("pigs").size() == 2 ); 56 | 57 | std::cout << t << std::endl; 58 | std::cout << t("this should never create a new entry") << std::endl; 59 | std::cout << t("oh noes") << std::endl; 60 | std::cout << t << std::endl; 61 | 62 | // translate 63 | #if 1 64 | { 65 | oak::tree stree; 66 | std::map< std::string, size_t > translation_map; 67 | translation_map["fruits"] = 0; 68 | translation_map["oranges"] = 1; 69 | translation_map["lemons"] = 2; 70 | translation_map["grapes"] = 3; 71 | translation_map["animals"] = 4; 72 | translation_map["pigs"] = 5; 73 | translation_map["guinea"] = 6; 74 | translation_map["thai"] = 7; 75 | translation_map["wombats"] = 8; 76 | translation_map["omg"] = 9; 77 | translation_map["oh noes"] = 10; 78 | 79 | stree = t.rekey(translation_map); 80 | std::cout << stree << std::endl; 81 | } 82 | 83 | { 84 | oak::tree stree; 85 | std::map< size_t, std::string > translation_map; 86 | translation_map[ 0] = "fruits"; 87 | translation_map[ 1] = "oranges"; 88 | translation_map[ 2] = "lemons"; 89 | translation_map[ 3] = "grapes"; 90 | translation_map[ 4] = "animals"; 91 | translation_map[ 5] = "pigs"; 92 | translation_map[ 6] = "guinea"; 93 | translation_map[ 7] = "thai"; 94 | translation_map[ 8] = "wombats"; 95 | translation_map[ 9] = "omg"; 96 | translation_map[10] = "oh noes"; 97 | 98 | stree = t.rekey(translation_map); 99 | std::cout << stree << std::endl; 100 | } 101 | #endif 102 | 103 | // working on leaves 104 | 105 | t["fruits"].clear(); 106 | std::cout << t << std::endl; 107 | 108 | t["fruits"] = t["animals"]["pigs"]; 109 | std::cout << t << std::endl; 110 | 111 | std::cout << "All ok." << std::endl; 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /oak.hpp: -------------------------------------------------------------------------------- 1 | // a simple tree container, zlib/libpng licensed. 2 | // - rlyeh ~~ listening to Buckethead - The Moltrail #2 3 | 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define OAK_VERSION "1.0.0" // (2015/10/25) Semantic versioning adherence; fix csv() with non-string keys 14 | 15 | namespace oak 16 | { 17 | // config 18 | # ifndef OAK_VERBOSE 19 | enum { OAK_VERBOSE = false }; 20 | # endif 21 | 22 | // tree class 23 | // [] means read-writeable (so is <<) 24 | // () means read-only access 25 | 26 | template< typename K, typename V = int, typename P = std::less< K > > 27 | class tree : public std::map< K, tree, P > { 28 | 29 | typedef typename std::map< K, tree, P > map; 30 | 31 | template< typename T > 32 | T zero() const { 33 | return std::pair().first; 34 | } 35 | 36 | template 37 | T &invalid() const { 38 | static T t; 39 | return t = T(), t; 40 | } 41 | 42 | V value; 43 | tree *parent; 44 | 45 | public: 46 | 47 | tree() : map(), value(zero()) { 48 | parent = this; 49 | } 50 | 51 | tree( const tree &t ) : map(), value(zero()) { 52 | parent = this; 53 | operator=(t); 54 | } 55 | 56 | // tree clone 57 | 58 | tree &operator=( const tree &t ) { 59 | if( this != &t ) { 60 | this->clear(); 61 | get() = zero(); 62 | operator+=(t); 63 | } 64 | return *this; 65 | } 66 | 67 | // tree merge 68 | 69 | tree &operator+=( const tree &t ) { 70 | if( this != &t ) { 71 | for( typename tree::const_iterator it = t.begin(), end = t.end(); it != end; ++it ) { 72 | this->map::insert( *it ); 73 | } 74 | get() = t.get(); 75 | } 76 | return *this; 77 | } 78 | 79 | // tree search ; const safe find: no insertions on new searches 80 | 81 | const tree &at( const K &t ) const { 82 | typename map::const_iterator find = this->find( t ); 83 | return find != this->end() ? find->second : invalid(); 84 | } 85 | 86 | // tree insertion 87 | 88 | tree &insert( const K &t ) { 89 | map &children = *this; 90 | ( children[t] = children[t] ).parent = this; 91 | return children[t]; 92 | } 93 | tree &erase( const K &t ) { 94 | typename map::iterator find = this->find(t); 95 | if( find != this->end() ) this->map::erase(t); 96 | return *this; 97 | } 98 | 99 | // recursive values 100 | 101 | V &get() { 102 | return value; 103 | } 104 | const V &get() const { 105 | return value; 106 | } 107 | 108 | template 109 | tree &set( const other &t ) { 110 | get() = t; 111 | return *this; 112 | } 113 | template 114 | tree &setup( const other &t ) { 115 | if( !is_root() ) { 116 | up().set(t).setup(t); 117 | } 118 | return *this; 119 | } 120 | template 121 | tree &setdown( const other &t ) { 122 | for( typename tree::iterator it = this->begin(), end = this->end(); it != end; ++it ) { 123 | it->second.set(t).setdown( t ); 124 | } 125 | return *this; 126 | } 127 | 128 | V getdown() const { 129 | V value = get(); 130 | for( typename tree::const_iterator it = this->begin(), end = this->end(); it != end; ++it ) { 131 | value += it->second.getdown(); 132 | } 133 | return value; 134 | } 135 | 136 | // sugars 137 | 138 | tree &clone( const tree &t ) { 139 | return operator=( t ); 140 | } 141 | tree &assign( const tree &t ) { 142 | return operator=( t ); 143 | } 144 | 145 | template 146 | tree &operator=( const other &t ) { 147 | return set(t); 148 | } 149 | template 150 | tree &operator+=( const other &t ) { 151 | return get() += t, *this; 152 | } 153 | 154 | tree &merge( const tree &t ) { 155 | return operator +=( t ); 156 | } 157 | tree &operator[]( const K &t ) { 158 | return insert( t ); 159 | } 160 | const tree &operator()( const K &t ) const { 161 | return at( t ); 162 | } 163 | 164 | bool empty( const K &t ) const { // @todo: subempty 165 | return this->find(t) == this->end(); 166 | } 167 | bool has( const K &t ) const { 168 | return !empty( t ); 169 | } 170 | 171 | bool is_valid() const { 172 | return this != &invalid(); 173 | } 174 | bool operator!() const { 175 | return !is_valid(); 176 | } 177 | 178 | const map &children() const { 179 | return *this; 180 | } 181 | map &children() { 182 | return *this; 183 | } 184 | 185 | tree &up() { 186 | return *parent; 187 | } 188 | const tree &up() const { 189 | return *parent; 190 | } 191 | 192 | bool is_root() const { 193 | return parent == this; 194 | } 195 | const tree &root() const { 196 | if( !is_root() ) return parent->root(); 197 | return *this; 198 | } 199 | tree &root() { 200 | if( !is_root() ) return parent->root(); 201 | return *this; 202 | } 203 | 204 | // tools 205 | 206 | template 207 | void csv( ostream &cout = std::cout, const std::string &prefix = std::string(), unsigned depth = 0 ) const { 208 | for( typename tree::const_iterator it = this->begin(), end = this->end(); it != end; ++it ) { 209 | cout << prefix << "/" << it->first << "," << it->second.get() << std::endl; 210 | std::stringstream ss; 211 | ss << prefix << "/" << it->first; 212 | it->second.csv( cout, ss.str(), depth + 1 ); 213 | } 214 | } 215 | 216 | std::string as_csv() const { 217 | std::stringstream ss; 218 | return csv( ss ), ss.str(); 219 | } 220 | 221 | template 222 | void print( ostream &cout = std::cout, unsigned depth = 0 ) const { 223 | std::string tabs( depth, '\t' ); 224 | for( typename tree::const_iterator it = this->begin(), end = this->end(); it != end; ++it ) { 225 | cout << tabs << "[" << this->size() << "] " << it->first << " (" << it->second.get() << ")"; 226 | if( !OAK_VERBOSE ) cout << std::endl; 227 | else cout << ".t=" << this << ",.p=" << parent << std::endl; 228 | it->second.print( cout, depth + 1 ); 229 | } 230 | } 231 | 232 | template 233 | void print( const std::map< K, U > &tmap, ostream &cout, unsigned depth = 0 ) const { 234 | std::string tabs( depth, '\t' ); 235 | for( typename tree::const_iterator it = this->begin(), end = this->end(); it != end; ++it ) { 236 | cout << tabs << "[" << this->size() << "] " << tmap.find( it->first )->second << " (" << it->second.get() << ")"; 237 | if( !OAK_VERBOSE ) cout << std::endl; 238 | else cout << ".t=" << this << ",.p=" << parent << std::endl; 239 | it->second.print( tmap, cout, depth + 1 ); 240 | } 241 | } 242 | 243 | template 244 | inline friend ostream &operator<<( ostream &os, const tree &self ) { 245 | return self.print( os ), os; 246 | } 247 | 248 | template 249 | tree rekey( const std::map< K, U > &map ) const { 250 | tree utree; 251 | for( typename tree::const_iterator it = this->begin(), end = this->end(); it != end; ++it ) { 252 | typename std::map< K, U >::const_iterator find = map.find( it->first ); 253 | assert( find != map.end() ); 254 | utree[ find->second ] += it->second.rekey( map ); 255 | utree[ find->second ].get() = it->second.get(); 256 | } 257 | return utree; 258 | } 259 | template 260 | tree rekey( const std::map< U, K > &map ) const { 261 | // this could be faster 262 | tree utree; 263 | for( typename std::map< U, K >::const_iterator it = map.begin(), end = map.end(); it != end; ++it ) { 264 | typename tree::const_iterator find = this->find( it->second ); 265 | if( find == this->end() ) continue; 266 | utree[ it->first ] += find->second.rekey( map ); 267 | utree[ it->first ].get() = find->second.get(); 268 | } 269 | return utree; 270 | } 271 | 272 | tree collapse() const { 273 | tree t; 274 | if( this->size() == 1 ) { 275 | return this->begin()->second.collapse(); 276 | } else { 277 | for( typename tree::const_iterator it = this->begin(), end = this->end(); it != end; ++it ) { 278 | t[ it->first ] += it->second.collapse(); 279 | t[ it->first ].get() = it->second.get(); 280 | } 281 | } 282 | return t; 283 | } 284 | 285 | V refresh() { 286 | V value = !this->size() ? get() : zero(); 287 | for( typename tree::iterator it = this->begin(), end = this->end(); it != end; ++it ) { 288 | value += it->second.refresh(); 289 | } 290 | return get() = value; 291 | } 292 | 293 | // c++03 case, user defined operator() 294 | // c++11 case, accept lambdas as arguments 295 | template 296 | tree &walk( const T &predicate ) { 297 | for( typename tree::iterator it = this->begin(), it_next = it; it != this->end(); it = it_next) { 298 | ++it_next; 299 | if( predicate(*this, it) ) { 300 | it->second.walk( predicate ); 301 | } 302 | } 303 | return *this; 304 | } 305 | 306 | // alias 307 | template 308 | tree &walk() { 309 | return walk( T() ); 310 | } 311 | }; 312 | } 313 | --------------------------------------------------------------------------------