mirror of https://github.com/deavmi/niknaks
Merge 5f66b99434
into b6106883da
This commit is contained in:
commit
c3b88d96b1
|
@ -11,12 +11,24 @@ 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)
|
||||
{
|
||||
import std.stdio : writeln;
|
||||
}
|
||||
|
||||
version(unittest)
|
||||
{
|
||||
import std.functional : toDelegate;
|
||||
|
||||
private void DebugTouch(T)(Tree!(T) node)
|
||||
{
|
||||
writeln("Touching tree node ", node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry of
|
||||
* some value of type `V`
|
||||
|
@ -597,4 +609,467 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A touching stratergy
|
||||
* that does nothing
|
||||
*/
|
||||
public template Nothing(T)
|
||||
{
|
||||
/**
|
||||
* Consumes a tree node
|
||||
* and does zilch with it
|
||||
*
|
||||
* Params:
|
||||
* treeNode = the node
|
||||
*/
|
||||
public void Nothing(Tree!(T));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Constructs a new tree with
|
||||
* the given value to set
|
||||
*
|
||||
* Params:
|
||||
* value = the value of
|
||||
* this tree node
|
||||
*/
|
||||
this(T value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tree without
|
||||
* associating any value with
|
||||
* itself
|
||||
*/
|
||||
this()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tree node's
|
||||
* associated value
|
||||
*
|
||||
* Params:
|
||||
* value = the valye
|
||||
*/
|
||||
public void setValue(T value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends another tree node
|
||||
* to the array of children
|
||||
* of this node's
|
||||
*
|
||||
* Params:
|
||||
* node = the tree node
|
||||
* to append
|
||||
*/
|
||||
public void appendNode(Tree!(T) node)
|
||||
{
|
||||
this.children ~= node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given tree node
|
||||
* from th array of children
|
||||
* of thie node's
|
||||
*
|
||||
* Params:
|
||||
* node = the tree node to
|
||||
* remove
|
||||
* Returns: `true` if the node
|
||||
* was found and then removed,
|
||||
* otherwise `false`
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
private static bool isTreeNodeType(E)()
|
||||
{
|
||||
return __traits(isSame, E, Tree!(T));
|
||||
}
|
||||
|
||||
private static bool isTreeValueType(E)()
|
||||
{
|
||||
return __traits(isSame, E, T);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a slice of the requested
|
||||
* type. This is either `Tree!(T)`
|
||||
* or `T` itself, therefore returning
|
||||
* an array of either
|
||||
*
|
||||
* Returns: an array of the requested
|
||||
* type of children
|
||||
*/
|
||||
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)[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the childrens'
|
||||
* associated values
|
||||
*
|
||||
* Returns: a `T[]`
|
||||
*/
|
||||
public T[] opSlice()
|
||||
{
|
||||
return opSlice!(T)();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element of the child
|
||||
* at the given index.
|
||||
*
|
||||
* The type `E` can be specified
|
||||
* as either `Tree!(T)` or `T`
|
||||
* which will hence return a node
|
||||
* from the children array at the
|
||||
* given index of that tyope (either
|
||||
* the child node or the child node's
|
||||
* value).
|
||||
*
|
||||
* Params:
|
||||
* idx = the index
|
||||
* Returns: the type `E`
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of
|
||||
* the child node at
|
||||
* the provided index
|
||||
*
|
||||
* Params:
|
||||
* idx = the index
|
||||
* Returns: the 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test out usage of the tree
|
||||
*/
|
||||
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");
|
||||
}
|
Loading…
Reference in New Issue