import RedBlackTree from '../lib/Tree.js' import * as ID from '../src/Util/ID.js' import { test, proxyConsole } from '../node_modules/cutest/cutest.mjs' import * as random from '../lib/random/random.js' proxyConsole() var numberOfRBTreeTests = 10000 function checkRedNodesDoNotHaveBlackChildren (t, tree) { let correct = true function traverse (n) { if (n == null) { return } if (n.isRed()) { if (n.left != null) { correct = correct && !n.left.isRed() } if (n.right != null) { correct = correct && !n.right.isRed() } } traverse(n.left) traverse(n.right) } traverse(tree.root) t.assert(correct, 'Red nodes do not have black children') } function checkBlackHeightOfSubTreesAreEqual (t, tree) { let correct = true function traverse (n) { if (n == null) { return 0 } var sub1 = traverse(n.left) var sub2 = traverse(n.right) if (sub1 !== sub2) { correct = false } if (n.isRed()) { return sub1 } else { return sub1 + 1 } } traverse(tree.root) t.assert(correct, 'Black-height of sub-trees are equal') } function checkRootNodeIsBlack (t, tree) { t.assert(tree.root == null || tree.root.isBlack(), 'root node is black') } test('RedBlack Tree', async function redBlackTree (t) { let tree = new RedBlackTree() tree.put({_id: ID.createID(8433, 0)}) tree.put({_id: ID.createID(12844, 0)}) tree.put({_id: ID.createID(1795, 0)}) tree.put({_id: ID.createID(30302, 0)}) tree.put({_id: ID.createID(64287)}) tree.delete(ID.createID(8433, 0)) tree.put({_id: ID.createID(28996)}) tree.delete(ID.createID(64287)) tree.put({_id: ID.createID(22721)}) checkRootNodeIsBlack(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree) }) test(`random tests (${numberOfRBTreeTests})`, async function randomRBTree (t) { let prng = random.createPRNG(t.getSeed() * 1000000000) let tree = new RedBlackTree() let elements = [] for (var i = 0; i < numberOfRBTreeTests; i++) { if (random.int32(prng, 0, 100) < 80) { // 80% chance to insert an element let obj = ID.createID(random.int32(prng, 0, numberOfRBTreeTests), random.int32(prng, 0, 1)) let nodeExists = tree.find(obj) if (nodeExists === null) { if (elements.some(e => e.equals(obj))) { t.assert(false, 'tree and elements contain different results') } elements.push(obj) tree.put({_id: obj}) } } else if (elements.length > 0) { // ~20% chance to delete an element var elem = random.oneOf(prng, elements) elements = elements.filter(function (e) { return !e.equals(elem) }) tree.delete(elem) } } checkRootNodeIsBlack(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree) // TEST if all nodes exist let allNodesExist = true for (let id of elements) { let node = tree.find(id) if (!node._id.equals(id)) { allNodesExist = false } } t.assert(allNodesExist, 'All inserted nodes exist') // TEST lower bound search let findAllNodesWithLowerBoundSerach = true for (let id of elements) { let node = tree.findWithLowerBound(id) if (!node._id.equals(id)) { findAllNodesWithLowerBoundSerach = false } } t.assert( findAllNodesWithLowerBoundSerach, 'Find every object with lower bound search' ) // TEST iteration (with lower bound search) let lowerBound = random.oneOf(prng, elements) let expectedResults = elements.filter((e, pos) => (lowerBound.lessThan(e) || e.equals(lowerBound)) && elements.indexOf(e) === pos ).length let actualResults = 0 tree.iterate(lowerBound, null, function (val) { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'Iterating over a tree with lower bound yields the right amount of results' ) expectedResults = elements.filter((e, pos) => elements.indexOf(e) === pos ).length actualResults = 0 tree.iterate(null, null, function (val) { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'iterating over a tree without bounds yields the right amount of results' ) let upperBound = random.oneOf(prng, elements) expectedResults = elements.filter((e, pos) => (e.lessThan(upperBound) || e.equals(upperBound)) && elements.indexOf(e) === pos ).length actualResults = 0 tree.iterate(null, upperBound, function (val) { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'iterating over a tree with upper bound yields the right amount of results' ) upperBound = random.oneOf(prng, elements) lowerBound = random.oneOf(prng, elements) if (upperBound.lessThan(lowerBound)) { [lowerBound, upperBound] = [upperBound, lowerBound] } expectedResults = elements.filter((e, pos) => (lowerBound.lessThan(e) || e.equals(lowerBound)) && (e.lessThan(upperBound) || e.equals(upperBound)) && elements.indexOf(e) === pos ).length actualResults = 0 tree.iterate(lowerBound, upperBound, function (val) { if (val == null) { t.assert(false, 'val is undefined!') } actualResults++ }) t.assert( expectedResults === actualResults, 'iterating over a tree with upper bound yields the right amount of results' ) })