diff --git a/source/niknaks/containers.d b/source/niknaks/containers.d index 9c1a01c..3519fc0 100644 --- a/source/niknaks/containers.d +++ b/source/niknaks/containers.d @@ -11,6 +11,8 @@ import std.datetime.stopwatch : StopWatch, AutoStart; import core.thread : Thread; import core.sync.condition : Condition; import std.functional : toDelegate; +import std.string : format; +import niknaks.arrays : removeResize; version(unittest) { @@ -597,4 +599,394 @@ unittest // Destroy the map (such that it ends the sweeper destroy(map); +} + +public template Always(T) +{ + public bool Always(Tree!(T) treeNode) + { + version(unittest) + { + import std.stdio : writeln; + writeln("Strat for: ", treeNode); + } + return true; + } +} + +public template Nothing(T) +{ + public void Nothing(Tree!(T) treeNode) + { + + } +} + +/** + * The inclusion stratergy which + * will be called upon the tree + * node prior to it being visited + * during a dfs operation. + * + * It is a predicate to determine + * whether or not the tree node + * in concern should be recursed + * upon. + */ +public template InclusionStratergy(T) +{ + public alias InclusionStratergy = bool delegate(Tree!(T) item); +} + +/** + * This is called on a tree node + * as part of the first action + * that takes place during the + * visitation of said node during + * a dfs operation. + */ +public template TouchStratergy(T) +{ + public alias TouchStratergy = void delegate(Tree!(T) item); +} + + + +// TODO: Technically this is a graph +public class Tree(T) +{ + private T value; + private Tree!(T)[] children; + + this(T value) + { + this.value = value; + } + + this() + { + + } + + public void setValue(T value) + { + this.value = value; + } + + public void appendNode(Tree!(T) node) + { + this.children ~= node; + } + + public bool removeNode(Tree!(T) node) + { + bool found = false; + size_t idx; + for(size_t i = 0; i < this.children.length; i++) + { + found = this.children[i] == node; + if(found) + { + idx = i; + break; + } + } + + if(found) + { + this.children = this.children.removeResize(idx); + return true; + } + + return false; + } + + // public T opIndex(size_t idx) + // { + // return idx < this.children.length ? this.children[idx].getValue() : T.init; + // } + + private static bool isTreeNodeType(E)() + { + return __traits(isSame, E, Tree!(T)); + } + + private static bool isTreeValueType(E)() + { + return __traits(isSame, E, T); + } + + public E[] opSlice(E)() + if(isTreeNodeType!(E) || isTreeValueType!(E)) + { + // If the children as tree nodes is requested + static if(isTreeNodeType!(E)) + { + return this.children; + } + // If the children as values themselves is requested + else static if(isTreeValueType!(E)) + { + T[] slice; + foreach(Tree!(T) tnode; this.children) + { + slice ~= tnode.value; + } + return slice; + // import std.algorithm.iteration : map; + // return map!(getValue)(this.children)[]; + } + } + + public T[] opSlice() + { + return opSlice!(T)(); + } + + public E opIndex(E)(size_t idx) + if(isTreeNodeType!(E) || isTreeValueType!(E)) + { + // If the cjild as a tree node is requested + static if(isTreeNodeType!(E)) + { + return this.children[idx]; + } + // If the child as a value itself is requested + else static if(isTreeValueType!(E)) + { + return this.children[idx].value; + } + } + + public T opIndex(size_t idx) + { + return opIndex!(T)(idx); + } + + public T[] dfs + ( + InclusionStratergy!(T) strat = toDelegate(&Always!(T)), + TouchStratergy!(T) touch = toDelegate(&Nothing!(T)) + ) + { + version(unittest) + { + writeln("dfs entry: ", this); + } + + T[] collected; + scope(exit) + { + version(unittest) + { + writeln("leaving node ", this, " with collected ", collected); + } + } + + // Touch + touch(this); // root[x] + + foreach(Tree!(T) child; this.children) // subtree[x], + { + if(strat(child)) + { + version(unittest) + { + writeln("dfs, strat good for child: ", child); + } + + // Visit + collected ~= child.dfs(strat, touch); + } + else + { + version(unittest) + { + writeln("dfs, strat ignored for child: ", child); + } + } + } + + // "Visit" + collected ~= this.value; + + + return collected; + } + + public override string toString() + { + return format("TreeNode [val: %s]", this.value); + } +} + + +version(unittest) +{ + import std.functional : toDelegate; + import std.stdio : writeln; + + private void DebugTouch(T)(Tree!(T) node) + { + writeln("Touching tree node ", node); + } +} + +unittest +{ + Tree!(string) treeOfStrings = new Tree!(string)("Top"); + + Tree!(string) subtree_1 = new Tree!(string)("1"); + Tree!(string) subtree_2 = new Tree!(string)("2"); + Tree!(string) subtree_3 = new Tree!(string)("3"); + + treeOfStrings.appendNode(subtree_1); + treeOfStrings.appendNode(subtree_2); + treeOfStrings.appendNode(subtree_3); + + + InclusionStratergy!(string) strat = toDelegate(&Always!(string)); + TouchStratergy!(string) touch = toDelegate(&DebugTouch!(string)); + + string[] result = treeOfStrings.dfs(strat, touch); + writeln("dfs: ", result); + + assert(result[0] == "1"); + assert(result[1] == "2"); + assert(result[2] == "3"); + assert(result[3] == "Top"); + + + auto i = treeOfStrings.opSlice!(Tree!(string))(); + writeln("Siblings: ", i); + assert(i[0] == subtree_1); + assert(i[1] == subtree_2); + assert(i[2] == subtree_3); + + auto p = treeOfStrings.opSlice!(string)(); + writeln("Siblings (vals): ", p); + assert(p == treeOfStrings[]); + + + assert(treeOfStrings.removeNode(subtree_1)); + assert(!treeOfStrings.removeNode(subtree_1)); +} + +/** + * A kind-of a tree which has the ability + * to linearize all of its nodes which + * results in performing a depth first + * search resulting in the collection of + * all nodes into a single array with + * elements on the left hand side being + * the most leafiest (and left-to-right + * on the same depth are in said order). + * + * It also marks a node as visited on + * entry to it via the dfs call to it. + * + * When dfs is performed, a child node + * is only recursed upon if it has not + * yet been visited. + * + * With all this, it means a graph of + * relations can be flattened into an + * array. + */ +public class VisitationTree(T) : Tree!(T) +{ + private bool visisted; + + /** + * Constructs a new node + * + * Params: + * value = the value + */ + this(T value) + { + super(value); + } + + /** + * Performs the linearization + * + * Returns: the linearized list + */ + public T[] linearize() + { + return dfs(toDelegate(&_shouldVisit), toDelegate(&_touch)); + } + + /** + * The inclusion startergy + * + * Params: + * tnode = the tree node + * Returns: `true` if not + * yet visited or incompatible + * node type + */ + private static bool _shouldVisit(Tree!(T) tnode) + { + VisitationTree!(T) vnode = cast(VisitationTree!(T))tnode; + return vnode && !vnode.isVisited(); + } + + /** + * The touching stratergy + * + * Only works on compatible + * tree nodes + * + * Params: + * tnode = the tree node + */ + private static void _touch(Tree!(T) tnode) + { + VisitationTree!(T) vnode = cast(VisitationTree!(T))tnode; + if(vnode) + { + vnode.mark(); + } + } + + /** + * Marks this node as + * visited + */ + private void mark() + { + this.visisted = true; + } + + /** + * Checks this node has been + * visited + * + * Returns: `true` if visited, + * otherwise `false` + */ + private bool isVisited() + { + return this.visisted; + } +} + +/** + * Tests out using the visitation tree + */ +unittest +{ + VisitationTree!(string) root = new VisitationTree!(string)("root"); + + VisitationTree!(string) thing = new VisitationTree!(string)("subtree"); + root.appendNode(thing); + thing.appendNode(root); + + string[] linearized = root.linearize(); + writeln(linearized); + + assert(linearized[0] == "subtree"); + assert(linearized[1] == "root"); } \ No newline at end of file