feat: highlight currently affected node

Fixes #7

Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
Matej Focko 2022-05-15 22:28:42 +02:00
parent 30b00e5d1a
commit 01f00eb330
Signed by: mfocko
GPG key ID: 7C47D46246790496
4 changed files with 64 additions and 40 deletions

33
avl.js
View file

@ -41,14 +41,15 @@ class AVLTree extends RankedTree {
if (rotatingAroundRoot) { if (rotatingAroundRoot) {
this.root = newRoot; this.root = newRoot;
} }
this.record("Fixing 0-child by single rotation: by z"); this.record("Fixing 0-child by single rotation: by z", z);
z.demote(); z.demote();
this.record("Fixing 0-child by single rotation: demoting z"); this.record("Fixing 0-child by single rotation: demoting z", z);
} else if (nodeDifference(y) == 1) { } else if (nodeDifference(y) == 1) {
rotateLeft(x); rotateLeft(x);
this.record( this.record(
"Fixing 0-child by double-rotation: first rotation by x" "Fixing 0-child by double-rotation: first rotation by x",
x
); );
newRoot = rotateRight(z); newRoot = rotateRight(z);
@ -56,17 +57,18 @@ class AVLTree extends RankedTree {
this.root = newRoot; this.root = newRoot;
} }
this.record( this.record(
"Fixing 0-child by double-rotation: second rotation by z" "Fixing 0-child by double-rotation: second rotation by z",
z
); );
y.promote(); y.promote();
this.record("Fixing 0-child by double-rotation: promoting y"); this.record("Fixing 0-child by double-rotation: promoting y", y);
x.demote(); x.demote();
this.record("Fixing 0-child by double-rotation: demoting x"); this.record("Fixing 0-child by double-rotation: demoting x", x);
z.demote(); z.demote();
this.record("Fixing 0-child by double-rotation: demoting z"); this.record("Fixing 0-child by double-rotation: demoting z", z);
} }
} }
@ -74,10 +76,12 @@ class AVLTree extends RankedTree {
let diffs = nodeDifferences(x.parent).sort(); let diffs = nodeDifferences(x.parent).sort();
while (x.parent && diffs.equals([0, 1])) { while (x.parent && diffs.equals([0, 1])) {
x.parent.promote(); x.parent.promote();
this.record(
"Insertion rebalance: promoting (0, 1) parent",
x.parent
);
x = x.parent; x = x.parent;
this.record("Insertion rebalance: promoting (0, 1) parent");
diffs = nodeDifferences(x.parent).sort(); diffs = nodeDifferences(x.parent).sort();
} }
@ -126,7 +130,7 @@ class AVLTree extends RankedTree {
break; break;
default: default:
rotateRight(y); rotateRight(y);
this.record("AVL deletion, fixing by double-rotation: by y"); this.record("AVL deletion, fixing by double-rotation: by y", y);
newRoot = rotateLeft(x); newRoot = rotateLeft(x);
break; break;
@ -134,13 +138,13 @@ class AVLTree extends RankedTree {
if (rotatingAroundRoot) { if (rotatingAroundRoot) {
this.root = newRoot; this.root = newRoot;
} }
this.record("AVL deletion, fixing by rotation by x"); this.record("AVL deletion, fixing by rotation by x", x);
[newRoot.left, newRoot.right, newRoot] [newRoot.left, newRoot.right, newRoot]
.filter((x) => x) .filter((x) => x)
.forEach((n) => { .forEach((n) => {
updateRank(n); updateRank(n);
this.record("Updating rank of nodes affected by rotations"); this.record("Updating rank of nodes affected by rotations", n);
}); });
return factor == 0; return factor == 0;
@ -154,7 +158,8 @@ class AVLTree extends RankedTree {
case 0: case 0:
updateRank(x); updateRank(x);
this.record( this.record(
"Updating rank of node affected by the propagation of delete" "Updating rank of node affected by the propagation of delete",
x
); );
return false; return false;

View file

@ -22,7 +22,7 @@ class RankedTree {
this.recorder = null; this.recorder = null;
} }
_getUnwrappedGraph() { _getUnwrappedGraph(highlightedNode) {
let result = []; let result = [];
let q = new Queue(); let q = new Queue();
@ -36,7 +36,11 @@ class RankedTree {
} }
let [value, rank] = [node.value, node.rank]; let [value, rank] = [node.value, node.rank];
result.push(`\t"Node(${value})" [label="${value}, ${rank}"];`); let highlight =
node === highlightedNode ? ', color="blue", penwidth=3' : "";
result.push(
`\t"Node(${value})" [label="${value}, ${rank}"${highlight}];`
);
[node.left, node.right] [node.left, node.right]
.filter((child) => child) .filter((child) => child)
@ -60,19 +64,19 @@ class RankedTree {
return result; return result;
} }
toDot() { toDot(highlightedNode) {
return ["digraph {", ...this._getUnwrappedGraph(), "}"]; return ["digraph {", ...this._getUnwrappedGraph(highlightedNode), "}"];
} }
toString() { toString() {
return this.toDot().join("\n"); return this.toDot().join("\n");
} }
record(message) { record(message, highlightedNode) {
if (!this.recorder) { if (!this.recorder) {
return; return;
} }
this.recorder.record(this, message); this.recorder.record(this, message, highlightedNode);
} }
rank() { rank() {
@ -92,7 +96,7 @@ class RankedTree {
if (!this.root) { if (!this.root) {
this.root = insertedNode; this.root = insertedNode;
this.record("Inserting key to the root"); this.record("Inserting key to the root", insertedNode);
return; return;
} }
@ -108,7 +112,7 @@ class RankedTree {
} else { } else {
parent.right = insertedNode; parent.right = insertedNode;
} }
this.record("Inserting key"); this.record("Inserting key", insertedNode);
this.insertRebalance(insertedNode); this.insertRebalance(insertedNode);
} }
@ -147,13 +151,14 @@ class RankedTree {
node.value = successor.value; node.value = successor.value;
successor.value = null; successor.value = null;
this.record( this.record(
"Replacing the value of deleted node with successor value" "Replacing the value of deleted node with successor value",
node
); );
return this.deleteNode(successor); return this.deleteNode(successor);
} }
this.record("Replacing the node with one of its children"); this.record("Replacing the node with one of its children", node);
return [y, parent]; return [y, parent];
} }

View file

@ -1,4 +1,4 @@
const DURATION = 500; const DURATION = 750;
class Record { class Record {
constructor(tree, message) { constructor(tree, message) {
@ -81,9 +81,11 @@ class Recorder {
throw "not implemented!"; throw "not implemented!";
} }
record(tree, message) { record(tree, message, highlightedNode) {
// TODO: adjust join if necessary // TODO: adjust join if necessary
this.states.push(new Record(tree.toDot().join(""), message)); this.states.push(
new Record(tree.toDot(highlightedNode).join(""), message)
);
this.render(); this.render();
} }
} }

38
wavl.js
View file

@ -37,25 +37,28 @@ class WAVLTree extends AVLTree {
this.root = newRoot; this.root = newRoot;
} }
this.record( this.record(
"Final step of deletion rebalance: single rotation by parent of y" "Final step of deletion rebalance: single rotation by parent of y",
y.parent
); );
y.promote(); y.promote();
this.record("Final step of deletion rebalance: promotion of y"); this.record("Final step of deletion rebalance: promotion of y", y);
z.demote(); z.demote();
this.record("Final step of deletion rebalance: demotion of z"); this.record("Final step of deletion rebalance: demotion of z", z);
if (z.type() == LEAF) { if (z.type() == LEAF) {
z.demote(); z.demote();
this.record( this.record(
"Final step of deletion rebalance: demotion of leaf z" "Final step of deletion rebalance: demotion of leaf z",
z
); );
} }
} else if (wDiff == 2 && v && v.parent) { } else if (wDiff == 2 && v && v.parent) {
rotateRight(v.parent); rotateRight(v.parent);
this.record( this.record(
"Final step of deletion rebalance: first of double rotation by parent of v" "Final step of deletion rebalance: first of double rotation by parent of v",
v.parent
); );
newRoot = rotateLeft(v.parent); newRoot = rotateLeft(v.parent);
@ -63,20 +66,23 @@ class WAVLTree extends AVLTree {
this.root = newRoot; this.root = newRoot;
} }
this.record( this.record(
"Final step of deletion rebalance: second of double rotation by parent of v" "Final step of deletion rebalance: second of double rotation by parent of v",
v.parent
); );
v.promote().promote(); v.promote().promote();
this.record( this.record(
"Final step of deletion rebalance: double promotion of v" "Final step of deletion rebalance: double promotion of v",
v
); );
y.demote(); y.demote();
this.record("Final step of deletion rebalance: demotion of y"); this.record("Final step of deletion rebalance: demotion of y", y);
z.demote().demote(); z.demote().demote();
this.record( this.record(
"Final step of deletion rebalance: double demotion of z" "Final step of deletion rebalance: double demotion of z",
z
); );
} }
} }
@ -97,11 +103,11 @@ class WAVLTree extends AVLTree {
(yDiff == 2 || nodeDifferences(y).equals([2, 2])) (yDiff == 2 || nodeDifferences(y).equals([2, 2]))
) { ) {
parent.demote(); parent.demote();
this.record("Propagating error by demoting parent"); this.record("Propagating error by demoting parent", parent);
if (yDiff != 2) { if (yDiff != 2) {
y.demote(); y.demote();
this.record("Demoting y"); this.record("Demoting y", y);
} }
x = parent; x = parent;
@ -151,12 +157,18 @@ class WAVLTree extends AVLTree {
deleteFixup(y, parent) { deleteFixup(y, parent) {
if (nodeDifferences(y).equals([2, 2])) { if (nodeDifferences(y).equals([2, 2])) {
y.demote(); y.demote();
this.record("Starting deletion rebalance by demoting (2, 2) node"); this.record(
"Starting deletion rebalance by demoting (2, 2) node",
y
);
parent = y.parent; parent = y.parent;
} else if (nodeDifferences(parent).equals([2, 2])) { } else if (nodeDifferences(parent).equals([2, 2])) {
parent.demote(); parent.demote();
this.record("Starting deletion rebalance by demoting (2, 2) node"); this.record(
"Starting deletion rebalance by demoting (2, 2) node",
parent
);
parent = parent.parent; parent = parent.parent;
} }