├── README.md ├── tree.cpp └── tree.sh /README.md: -------------------------------------------------------------------------------- 1 | # Apter Trees in C++ 2 | 3 | Apter Trees are a simpler representation of trees using just two vectors: `[nodevalues, 4 | parentindices]`. 5 | 6 | This repo contains a tree-like data type implemented in C++17, in the style of Stevan Apter in 7 | [Treetable: a case-study in q](http://archive.vector.org.uk/art10500340). 8 | 9 | ## Who cares? 10 | 11 | A tree is a data structure in which values have parent-child relationships to 12 | each other. They come in many forms. 13 | 14 | In most software, trees are implemented like a typical binary tree, where each 15 | node contains its own data and a pointer to each of its children, nominally just 16 | left and right, which are also nodes. The cycle continues. 17 | 18 | Using such a data structure can be challenging due to recursion and slow due to 19 | cache behavior in modern systems and frequent malloc()s. The concept of who 20 | "owns" a tree node in such a system can become complex in multi-layered 21 | software. 22 | 23 | Apter Trees are much faster, easier to reason about, and easier to implement. 24 | 25 | ## How it works 26 | 27 | An Apter tree is implemented as two same-sized arrays. 28 | 29 | One is a vector (array) of data (we'll call it `d`). These correspond to the 30 | values, or things that each node contains. 31 | 32 | The other is a vector of parent indices (`p`). The index of an item in the `d` 33 | vector is used as its key, which we will call `c` in the examples below. 34 | 35 | Often, the key/index `c` will just be an int. 36 | 37 | So, if we had a dog family tree in which Coco was the father of Molly and Arca, 38 | and Arca had a son named Cricket, you might have a data structure like: 39 | 40 | ``` 41 | tree.d = ["Coco", "Molly", "Arca","Cricket"] 42 | tree.p = [0,0,0,2] 43 | ``` 44 | 45 | A node with a key of `0` whose parent is zero is the root node. Apter trees 46 | require a root node, or the use of `-1` to mean "no parent", which is slightly 47 | less elegant so I'll ignore it. 48 | 49 | Computers are very, very fast at manipulating vectors. They're so much faster 50 | than pointer operations that comparisons of big-O notation for an algorithm 51 | don't play out in practice. 52 | 53 | ## Operations in psuedocode 54 | 55 | The technique is applicable in all languages. This library is written in C++ 56 | but I will use psuedocode to explain how it works. 57 | 58 | * Empty tree 59 | 60 | ``` 61 | tree() = { {d:[], p:[]} } # some sort of [data,parentidxs] vector 62 | ``` 63 | 64 | * Number of nodes 65 | 66 | ``` 67 | nodecnt(t) = { len(t.p) } 68 | ``` 69 | 70 | * Keys of all nodes 71 | 72 | ``` 73 | join(x,y) = { flatten(x,y) } # append to vec. i.e., x.push_back(y), x[]=y, etc. 74 | range(x,y) = { # Q til, APL/C++ iota; return [x, x+1, x+2, ...y-1] 75 | i=x; ret=[]; while(i++ 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | using TreeIndex = int; 8 | 9 | template vector emit(const vector x, string s) { 10 | cout << s << ": "; for(auto xx:x) { cout << xx << " "; } cout << "\n"; return x; 11 | } 12 | template X emit(const X x, string s) { cout << s << ": " << x << "\n"; return x; } 13 | template vector emit(const vector x) { for(auto xx:x) { cout << xx << " "; } cout << "\n"; return x; } 14 | template X emit(const X x) { cout << x << "\n"; return x; } 15 | 16 | template 17 | vector except(const vector x, const vector y) { 18 | vector r; r.reserve(x.size()); 19 | auto begy=begin(y), endy=end(y); 20 | for(auto xx:x) if(find(begy,endy,xx)==endy) r.push_back(xx); // XXX use copy here 21 | return r; 22 | } 23 | template 24 | vector except(const vector x, const X y) { 25 | vector r; r.reserve(x.size()); 26 | for(auto xx:x) if(xx!=y) r.push_back(xx); 27 | return r; 28 | } 29 | 30 | template 31 | // index X with y, and then X again with the result, until it returns the same thing twice 32 | // returns all steps 33 | // i.e., [ x[y], x[x[y]], x[x[x[y]]], ... 34 | vector exhaust(const vector x, X y) { 35 | X i=y,last; 36 | vector r; 37 | r.reserve(x.size()); 38 | while (1) { 39 | last=i; 40 | i=emit(x[i]); 41 | if(i==last) return r; 42 | else r.push_back(i); 43 | }; 44 | return r; 45 | } 46 | 47 | vector til(size_t n) { // non-stupid iota 48 | vector r; r.reserve(n); 49 | for(int i=0; i 54 | class Tree { 55 | public: 56 | vector x; 57 | vector p; 58 | vector operator[](vector path) { 59 | vector r; r.reserve(path.size()); 60 | for(auto p:path) r.push_back(x[p]); 61 | return r; 62 | } 63 | TreeIndex adopt(const TreeIndex parent, const TreeIndex child) { 64 | p[child]=parent; return child; 65 | } 66 | TreeIndex insert(const TreeIndex parent, const X item) { 67 | x.push_back(item); p.push_back(parent); return x.size()-1; 68 | } 69 | TreeIndex parent(const TreeIndex child) { 70 | return p[child]; 71 | } 72 | vector path(const TreeIndex child) { 73 | return exhaust(p, child); 74 | } 75 | vector leaves() { 76 | return except(til(size()), p); 77 | } 78 | size_t size() { 79 | return p.size(); 80 | } 81 | }; 82 | template 83 | ostream& operator<<(ostream &os, const Tree T) { 84 | TreeIndex n=0; int depth=0; 85 | for(auto xx:T.x) { 86 | for(int i=0;i a={1, 2, 3, 4, 5}; 95 | vector b={2,5,30}; 96 | emit(except(a,a[0])); 97 | emit(except(a,b)); 98 | } 99 | 100 | int main(void) { 101 | emit("tree"); 102 | Tree t; 103 | int r=emit(t.insert(0,"root")); 104 | int d=emit(t.insert(r,"dogs")); 105 | int c=emit(t.insert(r,"cats")); 106 | int a=emit(t.insert(r,"snakes")); 107 | int da=emit(t.insert(d,"min pin")); 108 | int db=emit(t.insert(d,"schnauzer")); 109 | int dba=emit(t.insert(db,"giant schnauzer")); 110 | int aa=emit(t.insert(a,"anaconda")); 111 | emit(t, "tree"); 112 | emit(t.leaves(), "leaves"); 113 | emit(t.path(5), "path(5)"); 114 | emit(t[t.path(5)], "t[path(5)]"); 115 | auto x=exhaust(t.p, 6); 116 | emit(x, "exhaust"); 117 | test_iter(); 118 | emit("fin"); 119 | } 120 | 121 | 122 | -------------------------------------------------------------------------------- /tree.sh: -------------------------------------------------------------------------------- 1 | g++ -std=c++1z tree.cpp -o tree 2>&1 >tree.err && ./tree 2 | 3 | --------------------------------------------------------------------------------