2 * Invisible Vector Library
3 * Andersson tree library
5 * based on the code from Julienne Walker
6 * further coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
7 * Understanding is not required. Only obedience.
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, version 3 of the License ONLY.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 module iv
.aatree
/*is aliced*/;
26 // ////////////////////////////////////////////////////////////////////////// //
27 final class AATree(TKey
, TValue
, bool stableIter
=true) if (is(typeof((TKey a
, TKey b
) => a
< b || a
> b
))) {
29 enum HEIGHT_LIMIT
= 64; // tallest allowable tree
34 static if (stableIter
) enum HasNodeList
= true; else enum HasNodeList
= false;
36 static if (is(TKey
== string
)) enum KeyIsString
= true; else enum KeyIsString
= false;
37 static if (is(TValue
== string
)) enum ValueIsString
= true; else enum ValueIsString
= false;
39 static if (is(TKey
== struct)) enum KeyIsStruct
= true; else enum KeyIsStruct
= false;
40 static if (is(TValue
== struct)) enum ValueIsStruct
= true; else enum ValueIsStruct
= false;
42 private static template ArgPfx(bool isStruct
) {
43 static if (isStruct
) enum ArgPfx
= "auto ref"; else enum ArgPfx
= "";
46 static final class Node
{
48 static if (is(typeof((TKey n
) => n
.clone()))) enum HasKeyClone
= true; else enum HasKeyClone
= false;
49 static if (is(typeof((TValue n
) => n
.clone()))) enum HasValueClone
= true; else enum HasValueClone
= false;
51 static if (is(typeof((TKey n
) { n
.release(); }))) enum HasKeyRelease
= true; else enum HasKeyRelease
= false;
52 static if (is(typeof((TValue n
) { n
.release(); }))) enum HasValueRelease
= true; else enum HasValueRelease
= false;
54 int level
; // horizontal level for balance
57 Node
[2] link
; // left (0) and right (1) links
58 static if (stableIter
) { public Node prev
, next
; }
60 // create sentinel node
62 //value = null; // simplifies some ops
64 link
.ptr
[Left
] = link
.ptr
[Right
] = this;
67 private enum ThisBodyMixin
= q
{
69 link
.ptr
[Left
] = link
.ptr
[Right
] = tree
.nil
;
70 static if (HasKeyClone
) {
71 static if (is(typeof(akey
is null))) {
72 if (akey
!is null) key
= akey
.clone();
79 static if (HasValueClone
) {
80 static if (is(typeof(avalue
is null))) {
81 if (avalue
!is null) value
= avalue
.clone();
83 value
= avalue
.clone();
91 mixin("this() (AATree tree, "~ArgPfx
!KeyIsStruct
~" TKey akey, "~ArgPfx
!ValueIsStruct
~" TValue avalue) {"~ThisBodyMixin
~"}");
94 //pragma(inline, true);
95 static if (HasKeyRelease
) {
96 static if (is(typeof(key
is null))) {
97 if (key
!is null) key
.release();
102 static if (HasValueRelease
) {
103 static if (is(typeof(value
is null))) {
104 if (value
!is null) value
.release();
113 Node root
; // top of the tree
114 Node nil
; // end of tree sentinel
115 usize treeSize
; // number of items (user-defined)
116 ulong modFrame
; // to invalidate walkers
118 static if (stableIter
) {
121 void addNodeToList (Node n
) {
122 pragma(inline
, true);
124 if (tail
!is null) tail
.next
= n
;
126 if (head
is null) head
= n
;
129 void removeNodeFromList (Node n
) {
130 pragma(inline
, true);
131 if (n
.prev
!is null) n
.prev
.next
= n
.next
; else head
= n
.next
;
132 if (n
.next
!is null) n
.next
.prev
= n
.prev
; else tail
= n
.prev
;
133 assert(head
is null || head
.prev
is null);
134 assert(tail
is null || tail
.next
is null);
137 Node
firstNode () { pragma(inline
, true); return head
; }
138 Node
lastNode () { pragma(inline
, true); return tail
; }
140 auto byNodes(bool fromHead
=true) () {
141 //alias TreeType = typeof(this);
142 static struct Iterator(bool fromHead
) {
145 Node it
; // current node
146 ulong modFrame
; // to sync with owner tree
148 this (AATree atree
) {
150 modFrame
= atree
.modFrame
;
151 static if (fromHead
) it
= tree
.firstNode
; else it
= tree
.lastNode
;
154 @property bool empty () const pure { pragma(inline
, true); return (it
is null || it
is tree
.nil || modFrame
!= tree
.modFrame
); }
155 @property Node
front () pure { pragma(inline
, true); return it
; }
156 @property auto save () @trusted {
157 pragma(inline
, true);
158 typeof(this) res
= void;
161 res
.modFrame
= modFrame
;
165 if (empty
) { it
= null; tree
= null; return; }
166 static if (fromHead
) it
= it
.next
; else it
= it
.prev
;
167 if (empty
) { it
= null; tree
= null; return; }
170 return Iterator
!fromHead(this);
173 alias fromFirstNode
= byNodes
!true;
174 alias fromLastNode
= byNodes
!false;
177 debug usize
maxTreeDepth () {
179 void descend (Node n
, usize depth
) {
180 if (n
is null || n
is nil
) {
181 if (depth
-1 > maxdepth
) maxdepth
= depth
-1;
184 descend(n
.link
[0], depth
+1);
185 descend(n
.link
[1], depth
+1);
193 // initialize sentinel
194 nil
= new Node(this);
200 ~this () { clear(); }
205 // destruction by rotation
207 if (it
.link
.ptr
[Left
] is nil
) {
209 save
= it
.link
.ptr
[Right
];
211 static if (stableIter
) removeNodeFromList(it
);
215 save
= it
.link
.ptr
[Left
];
216 it
.link
.ptr
[Left
] = save
.link
.ptr
[Right
];
217 save
.link
.ptr
[Right
] = it
;
222 // finalize destruction
228 private enum FindBodyMixin
= q
{
231 int cmp = (it
.key
< key ?
-1 : it
.key
> key ?
1 : 0);
233 it
= it
.link
.ptr
[(cmp < 0 ? Right
: Left
)];
236 return (it
!is nil ? it
: null);
239 static if (KeyIsString
) {
240 Node
find() (const(char)[] key
) { mixin(FindBodyMixin
); }
241 Node
opBinaryRight(string op
: "in") (const(char)[] key
) { pragma(inline
, true); return find(key
); }
243 mixin("Node find() ("~ArgPfx
!KeyIsStruct
~" TKey key) { "~FindBodyMixin
~" }");
244 mixin("Node opBinaryRight(string op : `in`) ("~ArgPfx
!KeyIsStruct
~" TKey key) { pragma(inline, true); return find(key); }");
248 private enum InsertBodyMixin
= q
{
251 static if (KeyIsString
) { static if (!is(TK
== string
)) { auto dkey
= key
.idup
; } else { alias dkey
= key
; } } else { alias dkey
= key
; }
252 static if (ValueIsString
) { static if (!is(TV
== string
)) { auto dvalue
= value
.idup
; } else { alias dvalue
= value
; } } else { alias dvalue
= value
; }
253 root
= new Node(this, dkey
, dvalue
);
254 static if (stableIter
) addNodeToList(root
);
255 //if (root is nil) return false;
258 Node
[HEIGHT_LIMIT
] path
;
260 // find a spot and save the path
263 dir
= (it
.key
< key ? Right
: Left
);
264 if (it
.link
.ptr
[dir
] is nil
) break;
265 it
= it
.link
.ptr
[dir
];
268 static if (KeyIsString
) { static if (!is(TK
== string
)) { auto dkey
= key
.idup
; } else { alias dkey
= key
; } } else { alias dkey
= key
; }
269 static if (ValueIsString
) { static if (!is(TV
== string
)) { auto dvalue
= value
.idup
; } else { alias dvalue
= value
; } } else { alias dvalue
= value
; }
270 it
.link
.ptr
[dir
] = new Node(this, dkey
, dvalue
);
271 static if (stableIter
) addNodeToList(it
.link
.ptr
[dir
]);
272 //if (it.link.ptr[dir] is nil) return false;
273 // walk back and rebalance
276 if (top
!= 0) dir
= (path
[top
-1].link
.ptr
[Right
] is path
[top
] ? Right
: Left
);
277 mixin(skew
!"path[top]");
278 mixin(split
!"path[top]");
281 path
[top
-1].link
.ptr
[dir
] = path
[top
];
292 static if (KeyIsString
&& ValueIsString
) {
293 bool insert(TK
, TV
) (TK key
, TV value
) if (is(TK
: const(char)[]) && is(TV
: const(char)[])) { mixin(InsertBodyMixin
); }
294 } else static if (KeyIsString
) {
295 mixin("bool insert(TK) (TK key, "~ArgPfx
!ValueIsStruct
~" TValue value) if (is(TK : const(char)[])) { "~InsertBodyMixin
~" }");
296 } else static if (ValueIsString
) {
297 mixin("bool insert(TV) ("~ArgPfx
!KeyIsStruct
~" TKey key, TV value) if (is(TV : const(char)[])) { "~InsertBodyMixin
~" }");
299 mixin("bool insert() ("~ArgPfx
!KeyIsStruct
~" TKey key, "~ArgPfx
!ValueIsStruct
~" TValue value) { "~InsertBodyMixin
~" }");
303 private enum RemoveBodyMixin
= q
{
304 if (root
is nil
) return false;
306 Node
[HEIGHT_LIMIT
] path
;
307 int top
= 0, dir
, cmp;
308 // find node to remove and save path
311 if (it
is nil
) return false;
312 cmp = (it
.key
< key ?
-1 : it
.key
> key ?
1 : 0);
314 dir
= (cmp < 0 ? Right
: Left
);
315 it
= it
.link
.ptr
[dir
];
317 // remove the found node
318 if (it
.link
.ptr
[Left
] is nil || it
.link
.ptr
[Right
] is nil
) {
320 int dir2
= (it
.link
.ptr
[Left
] is nil ? Right
: Left
);
323 path
[top
-1].link
.ptr
[dir
] = it
.link
.ptr
[dir2
];
325 root
= it
.link
.ptr
[Right
];
328 static if (stableIter
) removeNodeFromList(it
);
332 auto heir
= it
.link
.ptr
[Right
];
334 while (heir
.link
.ptr
[Left
] !is nil
) {
335 path
[top
++] = prev
= heir
;
336 heir
= heir
.link
.ptr
[Left
];
338 // order is important!
339 // (free item, replace item, free heir)
342 it
.value
= heir
.value
;
343 prev
.link
.ptr
[(prev
is it ? Right
: Left
)] = heir
.link
.ptr
[Right
];
344 static if (stableIter
) {
345 // replace `heir` with `it` in node list
346 removeNodeFromList(it
);
350 if (it
.prev
!is null) it
.prev
.next
= it
; else head
= it
;
351 if (it
.next
!is null) it
.next
.prev
= it
; else tail
= it
;
355 // walk back up and rebalance
358 if (top
!= 0) dir
= (path
[top
-1].link
.ptr
[Right
] is up ? Right
: Left
);
359 // rebalance (aka. black magic)
360 if (up
.link
.ptr
[Left
].level
< up
.level
-1 || up
.link
.ptr
[Right
].level
< up
.level
-1) {
361 if (up
.link
.ptr
[Right
].level
> --up
.level
) up
.link
.ptr
[Right
].level
= up
.level
;
362 // order is important!
364 mixin(skew
!"up.link.ptr[Right]");
365 mixin(skew
!"up.link.ptr[Right].link.ptr[Right]");
367 mixin(split
!"up.link.ptr[Right]");
371 path
[top
-1].link
.ptr
[dir
] = up
;
381 static if (KeyIsString
) {
382 bool remove() (const(char)[] key
) { mixin(RemoveBodyMixin
); }
384 mixin("bool remove() ("~ArgPfx
!KeyIsStruct
~" TKey key) { "~RemoveBodyMixin
~" }");
387 usize
size () const pure nothrow @safe @nogc { pragma(inline
, true); return treeSize
; }
389 auto fromMin () { pragma(inline
, true); return Walker(this, Left
); }
390 auto fromMax () { pragma(inline
, true); return Walker(this, Right
); }
393 // remove left horizontal links
394 enum skew(string t
) = "{\n"~
395 " if ("~t
~".link.ptr[Left].level == "~t
~".level && "~t
~".level != 0) {\n"~
396 " auto save = "~t
~".link.ptr[Left];\n"~
397 " "~t
~".link.ptr[Left] = save.link.ptr[Right];\n"~
398 " save.link.ptr[Right] = "~t
~";\n"~
403 // remove consecutive horizontal links
404 enum split(string t
) = "{\n"~
405 " if ("~t
~".link.ptr[Right].link.ptr[Right].level == "~t
~".level && "~t
~".level != 0) {\n"~
406 " auto save = "~t
~".link.ptr[Right];\n"~
407 " "~t
~".link.ptr[Right] = save.link.ptr[Left];\n"~
408 " save.link.ptr[Left] = "~t
~";\n"~
414 static struct Walker
{
415 nothrow @trusted @nogc:
417 AATree tree
; // paired tree
418 Node it
; // current node
419 Node
[HEIGHT_LIMIT
] path
; // traversal path (actually, pointer to Node)
420 usize top
; // top of stack
421 int curdir
; // direction
422 ulong modFrame
; // to sync with owner tree
426 this (AATree atree
, int dir
) {
429 modFrame
= tree
.modFrame
;
435 it
= it
.link
.ptr
[curdir
];
437 if (top
) it
= path
[top
-1];
440 @property bool empty () const pure { pragma(inline
, true); return (tree
is null || it
is tree
.nil || modFrame
!= tree
.modFrame
); }
441 Node
front () pure { pragma(inline
, true); return it
; }
443 @property auto save () {
450 res
.modFrame
= modFrame
;
454 // if TOS is it: now we should go opposite dir
455 // go opposite dir: pop TOS (we shouldn't return there)
457 if (tree
is null || modFrame
!= tree
.modFrame || it
is tree
.nil || top
== 0) { top
= 0; it
= tree
.nil
; tree
= null; return; }
459 if (it
is path
[top
-1]) {
460 // we should go right, and pop this branch
462 it
= it
.link
.ptr
[curdir^
1];
465 // stepped right branch, now go left again
466 it
= it
.link
.ptr
[curdir
];
470 if (top
) it
= path
[top
-1];
471 if (it
is tree
.nil
) { tree
= null; }
474 @property bool toMin () const pure { pragma(inline
, true); return (curdir
!= Left
); }
475 @property bool toMax () const pure { pragma(inline
, true); return (curdir
== Left
); }
481 // ////////////////////////////////////////////////////////////////////////// //
483 import std
.stdio
: writeln
;
486 void checkIterators(AATree) (AATree tree, int[] values) {
487 import std.conv : to;
489 { import std.stdio; writeln("*** size=", tree.size); }
490 foreach (/+auto+/ n; tree.fromMin) {
491 { import std.stdio; writeln(" curK=", curK, "; values[", curK, "]=", values[curK]); }
492 if (n.key != values[curK]) assert(0, "(0)Invalid key for key "~to!string(curK)~" ("~to!string(n.key)~","~to!string(n.value)~" : "~to!string(values[curK])~")");
493 if (n.value != curK+1) assert(0, "(0)Invalid value for key "~to!string(curK)~" ("~to!string(n.key)~","~to!string(n.value)~" : "~to!string(values[curK])~")");
496 curK = cast(int)tree.size;
497 { import std.stdio; writeln(" curK=", curK, "; size=", tree.size); }
498 foreach (/+auto+/ n; tree.fromMax) {
500 { import std.stdio; writeln(" curK=", curK, "; values[", curK, "]=", values[curK]); }
501 if (n.key != values[curK]) assert(0, "(1)Invalid key for key "~to!string(curK)~" ("~to!string(n.key)~","~to!string(n.value)~" : "~to!string(values[curK])~")");
502 if (n.value != curK+1) assert(0, "(1)Invalid value for key "~to!string(curK)~" ("~to!string(n.key)~","~to!string(n.value)~" : "~to!string(values[curK])~")");
507 void checkIterators(AATree
) (AATree tree
) {
508 import std
.conv
: to
;
509 //int curK = int.min, curV = int.min;
512 auto it = tree.fromMin();
515 writeln(" k=", it.front.key, "; v=", it.front.value);
519 { import std.stdio; writeln("---"); }
520 { auto it = tree.fromMax(); while (!it.empty) { import std.stdio; writeln(" k=", it.front.key, "; v=", it.front.value); it.popFront(); } }
522 int count
= 0, ln
= int.min
;
523 { auto it
= tree
.fromMin(); while (!it
.empty
) { assert(it
.front
.key
> ln
); ln
= it
.front
.key
; ++count
; it
.popFront(); } }
524 assert(count
== tree
.size
);
527 { auto it
= tree
.fromMax(); while (!it
.empty
) { assert(it
.front
.key
< ln
); ln
= it
.front
.key
; ++count
; it
.popFront(); } }
528 assert(count
== tree
.size
);
530 //{ import std.stdio; writeln(" ** size=", tree.size); }
531 foreach (/*auto*/ n
; tree
.fromMin
) {
532 //if (n.key <= curK) assert(0, "(0)Invalid key for key "~to!string(curK)~" ("~to!string(n.key)~","~to!string(n.value)~")");
533 //if (n.value <= curV) assert(0, "(0)Invalid value for key "~to!string(curK)~" ("~to!string(n.key)~","~to!string(n.value)~")");
539 //{ import std.stdio; writeln(" keys=", keys); writeln(" values=", values); }
540 foreach (/*auto*/ n
; tree
.fromMax
) {
541 if (n
.key
!= keys
[$-1]) assert(0, "(1)Invalid key for key "~to
!string(keys
.length
-1)~" ("~to
!string(n
.key
)~","~to
!string(n
.value
)~")");
542 if (n
.value
!= values
[$-1]) assert(0, "(1)Invalid value for key "~to
!string(keys
.length
-1)~" ("~to
!string(n
.key
)~","~to
!string(n
.value
)~")");
544 values
= values
[0..$-1];
548 void test (int[] values
) {
549 import std
.conv
: to
;
550 //{ import std.stdio; writeln("*** len=", values.length); }
551 auto tree
= new AATree
!(int, int, true)();
553 static if (tree
.HasNodeList
) {
554 void checkNodeList () {
555 auto n
= tree
.firstNode
;
559 //if (n.key <= ln) { import std.stdio; writeln("ln=", ln, "; key=", n.key); }
560 //assert(n.key > ln);
565 ///*if (count != tree.size)*/ { import std.stdio; writeln("count=", count, "; size=", tree.size); }
566 assert(count
== tree
.size
);
571 //assert(n.key < ln);
576 import std
.range
: enumerate
;
577 assert(count
== tree
.size
);
579 foreach (/*auto*/ idx
, /*auto*/ nn
; tree
.fromFirstNode
.enumerate
) { assert(count
== idx
); ++count
; }
580 assert(count
== tree
.size
);
582 foreach (/*auto*/ idx
, /*auto*/ nn
; tree
.fromLastNode
.enumerate
) { assert(count
== idx
); ++count
; }
583 assert(count
== tree
.size
);
584 if (count
!= tree
.size
) { import std
.stdio
; writeln("count=", count
, "; size=", tree
.size
); }
587 void checkNodeList () {}
590 for (int i
= 0; i
< values
.length
; ++i
) {
591 if (!tree
.insert(values
[i
], i
+1)) assert(0, "Failed to insert {0}"~to
!string(values
[i
]));
592 if (auto n
= tree
.find(values
[i
])) {
593 if (n
.value
!= i
+1) assert(0, "Invalid value for key "~to
!string(values
[i
]));
595 assert(0, "Could not find key "~to
!string(values
[i
]));
597 checkIterators(tree
);
600 checkIterators(tree
);
602 for (int i
= 0; i
< values
.length
; ++i
) {
603 for (int j
= 0; j
< i
; j
++) {
604 if (tree
.find(values
[j
])) assert(0, "Found deleted key {0}"~to
!string(values
[j
]));
606 for (int j
= i
; j
< values
.length
; j
++) {
607 if (auto n
= tree
.find(values
[j
])) {
608 if (n
.value
!= j
+1) assert(0, "Invalid value for key {0}"~to
!string(values
[j
]));
610 assert(0, "Could not find key {0}"~to
!string(values
[j
]));
613 if (!tree
.remove(values
[i
])) assert(0, "Failed to delete {0}"~to
!string(values
[i
]));
614 checkIterators(tree
);
619 writeln("test00 (0)");
659 writeln("test00 (1)");
660 foreach (int count
; 0..1000) {
661 auto a
= new int[](100);
662 for (int i
= 0; i
< a
.length
; ++i
) {
666 import std
.random
: uniform
;
668 r
= uniform
!"[]"(r
.min
, r
.max
);
669 for (int j
= 0; j
< i
; ++j
) {
670 if (a
[j
] == r
) { dup
= true; break; }
680 // ////////////////////////////////////////////////////////////////////////// //
684 static final class Data
{
689 this (usize aidx
, string aword
) { idx
= aidx
; word
= aword
; }
692 //writeln("cloning #", idx, "[", word, "](", cloneCount, "+1)");
693 auto res
= new Data(idx
, word
);
694 res
.cloneCount
= cloneCount
+1;
699 //writeln("releasing #", idx, "[", word, "](", cloneCount, ")");
705 auto tree
= new AATree
!(string
, Data
, true);
707 writeln("creating tree...");
709 foreach (string line
; File("/usr/share/dict/words").byLineCopy
) {
714 import std
.random
: randomShuffle
, uniform
;
717 foreach (immutable idx
, string w
; words
) tree
.insert(w
, new Data(idx
, w
));
719 debug { writeln("tree items: ", tree
.size
, "; max tree depth: ", tree
.maxTreeDepth
); }
721 char[] key
= "supernatural".dup
;
724 key
= "motherfucker".dup
;
725 assert(key
!in tree
);
726 tree
.insert(key
, new Data(words
.length
, key
.idup
));
732 foreach (/*auto*/ node
; tree
.fromMin
) {
733 assert(ww
.length
== 0 || ww
< node
.key
);
738 foreach (/*auto*/ node
; tree
.fromMax
) {
739 assert(ww
.length
== 0 || ww
> node
.key
);
743 import std
.range
: enumerate
;
745 foreach (immutable idx
, /*auto*/ node
; tree
.fromFirstNode
.enumerate
) assert(node
.key
== words
[idx
]);
746 foreach (immutable idx
, /*auto*/ node
; tree
.fromLastNode
.enumerate
) assert(node
.key
== words
[words
.length
-idx
-1]);
751 writeln("removing elements from tree...");
754 while (words
.length
!= 0) {
755 if (count
++%128 == 0) {
756 stdout
.write("\r", words
.length
, " items left\x1b[K");
759 import std
.algorithm
.mutation
: remove
;
760 auto idx
= uniform
!"[)"(0, words
.length
);
762 words
= words
.remove(idx
);
763 if (!tree
.remove(cast(const(char)[])w
)) assert(0, "boo!");
766 stdout
.writeln("\r0 items left\x1b[K");
768 writeln("clearing tree...");
773 // ////////////////////////////////////////////////////////////////////////// //