BkTree: Add trace, improve shell, and bugfix delete.

The shell has given me an idea.....

We could have a CLI module to Pepperminty Wiki that allow admins to run 
longer-running tasks without them getting killed 1/2 way through because 
of a timeout.
This commit is contained in:
Starbeamrainbowlabs 2020-03-09 21:27:20 +00:00
parent 2f901642fe
commit 296139049a
Signed by: sbrl
GPG Key ID: 1BE5172E637709C2
3 changed files with 78 additions and 15 deletions

View File

@ -104,7 +104,7 @@ class BkTree
// if($string == "bunny") echo("\nStart $string\n");
$next_node = $this->box->get("node|$starting_node_id"); // Grab the root to start with
$next_node_id = 0;
$next_node_id = $starting_node_id;
$depth = 0; $visted = 0;
while(true) {
$visted++;
@ -160,10 +160,11 @@ class BkTree
$node_target_id = $node_target->children->$distance;
$node_target = $this->box->get("node|$node_target_id");
$stack[] = [ "node" => $node_target, "id" => $node_target->children->$distance ];
$stack[] = [ "node" => $node_target, "id" => $node_target_id ];
}
$parent = end($stack); // The last item on the stack is the parent node
// The last item but 1 on the stack is the parent node
$parent = $stack[count($stack) - 2];
// 1. Delete the connection from parent -> target
foreach($parent["node"]->children as $distance -> $id) {
@ -180,16 +181,53 @@ class BkTree
// 2. Iterate over the target's children (if any) and re-hang them from the parent
// NOTE: We need to be careful that the characteristics of the tree are preserved. We should test this by tracing a node's location in the tree and purposefully removing nodes in the chain and see if the results returned as still the same
//
// Hang the now orphaned children from the parent
// Hang the now orphaned children and all their decendants from the parent
foreach($node_target->children as $distance -> $id) {
$orphan = $this->box->get("node|$id");
// TODO: Optimise this
$this->box->delete("node|$id"); // Delete the orphan node
$this->add($orphan->value, $parent["id"]); // Re-hang it from the parent
$substack = [ [ "node" => $orphan, "id" => $id ] ]; $substack_top = 0;
while($substack_top >= 0) {
$next = $substack[$substack_top];
unset($substack[$substack_top]);
$substack_top--;
$this->box->delete("node|{$next["id"]}"); // Delete the orphan node
$this->add($next["node"], $parent["id"]); // Re-hang it from the parent
foreach($next["node"]->children as $distance => $sub_id) {
$substack[++$substack_top] = [
"node" => $this->box->get("node|$sub_id"),
"id" => $sub_id
];
}
}
}
// Delete the target node
$this->box->delete("node|$node_target_id");
return true;
}
public function trace(string $string) {
$stack = [
(object) [ "node" => $this->box->get("node|0"), "id" => 0 ]
];
$node_target = $stack[0]->node;
while($node_target->value !== $string) {
$distance = levenshtein($string, $node_target->value, $this->cost_insert, $this->cost_replace, $this->cost_delete);
var_dump($node_target);
// Failed to recurse to find the node with the value in question
if(!isset($node_target->children->$distance))
return null;
$node_target_id = $node_target->children->$distance;
$node_target = $this->box->get("node|$node_target_id");
$stack[] = (object) [ "node" => $node_target, "id" => $node_target_id ];
}
return $stack;
}
/**
@ -259,8 +297,7 @@ class BkTree
// echo("[lookup] Recursing on child ".$this->box->get("node|$child->id")->value." (distance $child->distance)\n");
// Push the node onto the stack
// Note that it doesn't actually matter that the stack isn't an accurate representation of ancestor nodes at any given time here. The stack is really a hybrid between a stack and a queue, having features of both.
$stack_top++;
$stack[$stack_top] = $this->box->get("node|{$node_current->children->$child_distance}");
$stack[++$stack_top] = $this->box->get("node|{$node_current->children->$child_distance}");
}
}

View File

@ -134,7 +134,7 @@ class JsonStorageBox {
/**
* Deletes an item from the data store.
* @param string $key The key of the item to delete.
* @return bool Whether it was really deleted or not. Note that if it doesn't exist, then it can't be deleted.
* @return bool Whether it was really deleted or not. Note that if it doesn't exist, then it can't be deleted. Note also that if a node is deleted before being persisted to disk, this will return false when in actuality it was deleted successfully.
*/
public function delete(string $key) : bool {
// Remove it from the cache

View File

@ -73,7 +73,7 @@ else
echo("Tree created in ".($tree["time"]*1000)."ms\n");
$tree = $tree["value"];
writegraph(); exit();
// writegraph(); exit();
function test_auto() {
global $tree;
@ -98,7 +98,8 @@ while(true) {
readline_add_history($line);
if($line[0] == ".") {
switch ($line) {
$parts = explode(" ", $line, 2);
switch ($parts[0]) {
case ".quit":
case ".exit":
$result = time_callable(function() use ($tree) {
@ -110,20 +111,45 @@ while(true) {
case ".help":
echo("dot commands:
.exit Exit, saving edits to the tree to disk
.writegraph Write a graphviz dot file to disk representing the tree in the current directory
.stats Compute statistics about the tree
.exit Exit, saving edits to the tree to disk
.writegraph Write a graphviz dot file to disk representing the tree in the current directory
.stats Compute statistics about the tree
.trace {{string}} Trace the path through the tree to {{string}}
.remove {{string}} Delete {{string}} from the tree
.add {{string}} Add {{string}} to the tree
");
break;
case ".writegraph":
writegraph();
break;
case ".remove":
$start_time = microtime(true);
$result = $tree->remove($parts[1]);
echo("{$parts[1]}".($result?"":" not")." successfully deleted from the tree in ".round((microtime(true) - $start_time)*1000)."ms\n");
break;
case ".add":
$start_time = microtime(true);
$depth = $tree->add($parts[1]);
echo("{$parts[1]} successfully added to the tree at depth $depth in ".round((microtime(true) - $start_time)*1000)."ms\n");
break;
case ".trace":
$trace = $tree->trace($parts[1]);
foreach($trace as $depth => $item) {
echo("$depth: {$item->node->value} (#$item->id)\n");
}
break;
case ".stats":
echo("Tree stats: ");
var_dump($tree->stats());
break;
default:
echo("Error: Unknown dot-command {$parts[0]} (try .help)\n");
break;
}
continue;
}