Skip to content

Commit 80cfb27

Browse files
make-github-pseudonymous-againAurélien Ooms
authored andcommitted
🚧 progress: First draft without explicit leaves.
Everything went as planned except that we needed to mock the child of the deleted node when it is an implicit leaf. The losses: This change puts additional load on: - delete_case{3,4,5}, - insert_case3, and - rotate_{left,right} This load comes from nullchecks before color checks and parent assignments. These checks should only be needed at the lowest levels of the fixed path because of the red-black tree properties. They could perhaps be circumvented entirely by adding additional mocked leaves to the sibling of the child of the deleted node. This should somehow be amortized by the facts that there is at most one deletion per node created and that each node now costs less to create (because of the allowed null pointers, see gains). If property access is always preceded by a nullcheck and if an explicit preceding nullcheck is somehow optimized away by the compiler, then perhaps nothing needs to be done. We have to profile this. The gains: - all Leaf children are replaced by null (>1/6 space save) - all isLeaf() calls are replaced by nullchecks: really faster? Should be since I assume each method call must be preceded by a null check internally? Also we should rewrite delete_one_child to completely avoid this mocking in case the deleted node is red. I left a TODO note about this. This is progress on #104.
1 parent 3dc424d commit 80cfb27

24 files changed

+87
-92
lines changed

mangle.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
"minify": {
33
"mangle": {
44
"properties": {
5-
"regex": "^(_color|isLeaf)$"
5+
"regex": "^_color$"
66
}
77
}
88
},
99
"props": {
1010
"props": {
11-
"$_color": "c",
12-
"$isLeaf": "L"
11+
"$_color": "c"
1312
}
1413
}
1514
}

src/debug/_debug.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import assert from 'assert';
22
import Node from '../types/Node.js';
3-
import Leaf from '../types/Leaf.js';
43
import BLACK from '../color/BLACK.js';
54

65
/**
@@ -14,15 +13,11 @@ const _debug = ({red, black}) => {
1413
* Recursively constructs a prettyprint string for the red-black tree rooted at
1514
* <code>root</code>.
1615
*
17-
* @param {Node|Leaf} root - The root of the tree.
16+
* @param {Node} root - The root of the tree.
1817
* @returns {string}
1918
*/
2019
const debug = (root) => {
21-
assert(root instanceof Node || root instanceof Leaf);
22-
if (root.isLeaf()) {
23-
assert(root instanceof Leaf);
24-
return black('L');
25-
}
20+
if (root === null) return black('L');
2621

2722
assert(root instanceof Node);
2823

src/deletion/delete_case2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const delete_case2 = (n) => {
2525
assert(n.parent !== null);
2626

2727
const s = sibling(n);
28+
assert(s instanceof Node);
2829

2930
/**
3031
* If n's sibling is red, prepare for and go to case 4.

src/deletion/delete_case3.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ const delete_case3 = (n) => {
3434
* B >B
3535
* / \ / \
3636
* >B B B R
37-
* / \ / \ --> / \ / \
38-
* - - B B - - B B
37+
* / \ / \ --> / \ / \
38+
* - - B B - - B B
3939
* / \ / \ / \ / \
4040
* - - - - - - - -
4141
*/
4242
if (
4343
n.parent._color === BLACK &&
44-
s.left._color === BLACK &&
45-
s.right._color === BLACK
44+
(s.left === null || s.left._color === BLACK) &&
45+
(s.right === null || s.right._color === BLACK)
4646
) {
4747
s._color = RED;
4848
delete_case1(n.parent);

src/deletion/delete_case4.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ const delete_case4 = (n) => {
2626
assert(s instanceof Node);
2727
assert(s._color === BLACK);
2828
assert(
29-
n.parent._color === RED || s.left._color === RED || s.right._color === RED,
29+
n.parent._color === RED ||
30+
s.left?._color === RED ||
31+
s.right?._color === RED,
3032
);
3133

3234
/**
@@ -46,8 +48,8 @@ const delete_case4 = (n) => {
4648
if (
4749
// The parent color test is always true when coming from case 2
4850
n.parent._color === RED &&
49-
s.left._color === BLACK &&
50-
s.right._color === BLACK
51+
(s.left === null || s.left._color === BLACK) &&
52+
(s.right === null || s.right._color === BLACK)
5153
) {
5254
s._color = RED;
5355
n.parent._color = BLACK;

src/deletion/delete_case5.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const delete_case5 = (n) => {
2727
const s = sibling(n);
2828
assert(s instanceof Node);
2929
assert(s._color === BLACK);
30-
assert(s.left._color === RED || s.right._color === RED);
30+
assert(s.left?._color === RED || s.right?._color === RED);
3131

3232
// The following statements just force the red n's sibling child to be on
3333
// the left of the left of the parent, or right of the right, so case 6
@@ -44,11 +44,14 @@ const delete_case5 = (n) => {
4444
* / \
4545
* - -
4646
*/
47-
if (n === n.parent.left && s.right._color === BLACK) {
47+
if (n === n.parent.left && (s.right === null || s.right._color === BLACK)) {
4848
s._color = RED;
4949
s.left._color = BLACK;
5050
rotate_right(s);
51-
} else if (n === n.parent.right && s.left._color === BLACK) {
51+
} else if (
52+
n === n.parent.right &&
53+
(s.left === null || s.left._color === BLACK)
54+
) {
5255
/**
5356
* ? ?
5457
* / \ / \

src/deletion/delete_mocked_leaf.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import assert from 'assert';
2+
import Leaf from '../types/Leaf.js';
3+
4+
/**
5+
* Delete leaf L.
6+
*
7+
* @param {Leaf} L - The leaf to delete.
8+
*/
9+
const delete_mocked_leaf = (L) => {
10+
assert(L instanceof Leaf);
11+
assert(L.parent !== null);
12+
13+
if (L === L.parent.left) L.parent.left = null;
14+
else L.parent.right = null;
15+
};
16+
17+
export default delete_mocked_leaf;

src/deletion/delete_one_child.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Leaf from '../types/Leaf.js';
77
import replace_node from './replace_node.js';
88
import delete_case2 from './delete_case2.js';
99

10+
import delete_mocked_leaf from './delete_mocked_leaf.js';
11+
1012
/**
1113
* Delete a node <code>n</code> that has at most a single non-leaf child.
1214
*
@@ -25,9 +27,12 @@ const delete_one_child = (n) => {
2527
// The right child of n is always a LEAF because either n is a subtree
2628
// predecessor or it is the only child of its parent by the red-black tree
2729
// properties
28-
assert(n.right instanceof Leaf);
30+
assert(n.right === null);
31+
32+
// Mock leaf if there is no left child
33+
const child = n.left === null ? new Leaf(null) : n.left;
2934

30-
const child = n.left;
35+
// TODO skip creating mocked leaf if n._color === RED
3136

3237
// Replace n with its left child
3338
replace_node(n, child);
@@ -48,6 +53,8 @@ const delete_one_child = (n) => {
4853
// child suffices. This is a NO-OP.
4954
assert(child._color === BLACK);
5055
}
56+
57+
if (child instanceof Leaf) delete_mocked_leaf(child);
5158
};
5259

5360
export default delete_one_child;

src/family/predecessor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const predecessor = (node) => {
1212
assert(node.left instanceof Node);
1313
let pred = node.left;
1414

15-
while (!pred.right.isLeaf()) {
15+
while (pred.right !== null) {
1616
assert(pred.right instanceof Node);
1717
pred = pred.right;
1818
}

src/family/sibling.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import Leaf from '../types/Leaf.js';
66
* Computes the sibling of the input node.
77
*
88
* @param {Node|Leaf} node - The input node.
9-
* @returns {Node|Leaf}
9+
* @returns {Node}
1010
*/
1111
const sibling = (node) => {
1212
assert(node instanceof Node || node instanceof Leaf);
13-
// We only use this function when node HAS a sibling.
13+
// We only use this function when node HAS a non-leaf sibling.
1414
assert(node.parent !== null);
1515

1616
return node === node.parent.left ? node.parent.right : node.parent.left;

0 commit comments

Comments
 (0)