Initial import with skill sheet working

This commit is contained in:
2024-12-04 00:11:23 +01:00
commit 9050c80ab4
4488 changed files with 671048 additions and 0 deletions

64
node_modules/@seald-io/binary-search-tree/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,64 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.3] - 2023-01-19
### Changed
- Correctly log object keys [#1](https://github.com/seald/node-binary-search-tree/pull/1)
## [1.0.2] - 2021-05-19
### Changed
- Specify files to be included in published version, v1.0.1 contained cache,
which contains, a now revoked, npm authentication token.
## [1.0.1] - 2021-05-19
### Added
- Added a changelog.
### Changed
- Removed `underscore` dependency which was used only in the tests.
## [1.0.0] - 2021-05-17
This version should be a drop-in replacement for `binary-search-tree@0.2.6`
provided you use modern browsers / versions of Node.js since ES6 features are
now used (such as `class` and `const` / `let`).
### Changed
- Update `homepage` & `repository` fields in the `package.json`
- New maintainer [seald](https://github.com/seald/) and new package
name [@seald-io/binary-search-tree](https://www.npmjs.com/package/@seald-io/binary-search-tree)
;
- Added `lockfileVersion: 2` `package-lock.json`;
- Modernized some of the code with ES6 features (`class`, `const` & `let`);
- Uses [`standard`](https://standardjs.com/) to lint the code (which removes all
unnecessary semicolons);
- Updated dependencies;
### Removed
- Compatibility with old browsers and old version of Node.js that don't support
ES6 features.
### Security
- This version no longer
uses [a vulnerable version of `underscore`](https://github.com/advisories/GHSA-cf4h-3jhx-xvhq)
.
## [0.2.6] - 2016-02-28
See [original repo](https://github.com/louischatriot/node-binary-search-tree)

19
node_modules/@seald-io/binary-search-tree/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2013 Louis Chatriot <louis.chatriot@gmail.com>
Copyright (c) 2021 Seald [contact@seald.io](mailto:contact@seald.io);
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

130
node_modules/@seald-io/binary-search-tree/README.md generated vendored Normal file
View File

@ -0,0 +1,130 @@
# Binary search trees for Node.js
This module is a fork
of [node-binary-search-tree](https://github.com/louischatriot/node-binary-search-tree)
written by Louis Chatriot for storing indexes
in [nedb](https://github.com/louischatriot/nedb).
Since the original maintainer doesn't support these packages anymore, we forked
them (here is [nedb](https://github.com/seald/nedb)) and maintain them for the
needs of [Seald](https://www.seald.io).
Two implementations of binary search
tree: [basic](http://en.wikipedia.org/wiki/Binary_search_tree)
and [AVL](http://en.wikipedia.org/wiki/AVL_tree) (a kind of self-balancing
binmary search tree).
## Installation and tests
Package name is `@seald-io/binary-search-tree`.
```bash
npm install @seald-io/binary-search-tree
```
If you want to run the tests, you'll have to clone the repository:
```bash
git clone https://github.com/seald/node-binary-search-tree
npm install
npm test
```
## Usage
The API mainly provides 3 functions: `insert`, `search` and `delete`. If you do
not create a unique-type binary search tree, you can store multiple pieces of
data for the same key. Doing so with a unique-type BST will result in an error
being thrown. Data is always returned as an array, and you can delete all data
relating to a given key, or just one piece of data.
Values inserted can be anything except `undefined`.
```javascript
const BinarySearchTree = require('binary-search-tree').BinarySearchTree
const AVLTree = require('binary-search-tree').AVLTree // Same API as BinarySearchTree
// Creating a binary search tree
const bst = new BinarySearchTree()
// Inserting some data
bst.insert(15, 'some data for key 15')
bst.insert(12, 'something else')
bst.insert(18, 'hello')
// You can insert multiple pieces of data for the same key
// if your tree doesn't enforce a unique constraint
bst.insert(18, 'world')
// Retrieving data (always returned as an array of all data stored for this key)
bst.search(15) // Equal to ['some data for key 15']
bst.search(18) // Equal to ['hello', 'world']
bst.search(1) // Equal to []
// Search between bounds with a MongoDB-like query
// Data is returned in key order
// Note the difference between $lt (less than) and $gte (less than OR EQUAL)
bst.betweenBounds({ $lt: 18, $gte: 12 }) // Equal to ['something else', 'some data for key 15']
// Deleting all the data relating to a key
bst.delete(15) // bst.search(15) will now give []
bst.delete(18, 'world') // bst.search(18) will now give ['hello']
```
There are three optional parameters you can pass the BST constructor, allowing
you to enforce a key-uniqueness constraint, use a custom function to compare
keys and use a custom function to check whether values are equal. These
parameters are all passed in an object.
### Uniqueness
```javascript
const bst = new BinarySearchTree({ unique: true });
bst.insert(10, 'hello');
bst.insert(10, 'world'); // Will throw an error
```
### Custom key comparison
```javascript
// Custom key comparison function
// It needs to return a negative number if a is less than b,
// a positive number if a is greater than b
// and 0 if they are equal
// If none is provided, the default one can compare numbers, dates and strings
// which are the most common usecases
const compareKeys = (a, b) => {
if (a.age < b.age) return -1
if (a.age > b.age) return 1
return 0
}
// Now we can use objects with an 'age' property as keys
const bst = new BinarySearchTree({ compareKeys })
bst.insert({ age: 23 }, 'Mark')
bst.insert({ age: 47 }, 'Franck')
```
### Custom value checking
```javascript
// Custom value equality checking function used when we try to just delete one piece of data
// Returns true if a and b are considered the same, false otherwise
// The default function is able to compare numbers and strings
const checkValueEquality = (a, b) => a.length === b.length
var bst = new BinarySearchTree({ checkValueEquality })
bst.insert(10, 'hello')
bst.insert(10, 'world')
bst.insert(10, 'howdoyoudo')
bst.delete(10, 'abcde')
bst.search(10) // Returns ['howdoyoudo']
```
## License
The package is released under the MIT License as the original package.
See LICENSE.md.

2
node_modules/@seald-io/binary-search-tree/index.js generated vendored Normal file
View File

@ -0,0 +1,2 @@
module.exports.BinarySearchTree = require('./lib/bst')
module.exports.AVLTree = require('./lib/avltree')

View File

@ -0,0 +1,412 @@
/**
* Self-balancing binary search tree using the AVL implementation
*/
const BinarySearchTree = require('./bst')
const customUtils = require('./customUtils')
class AVLTree {
/**
* Constructor
* We can't use a direct pointer to the root node (as in the simple binary search tree)
* as the root will change during tree rotations
* @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not
* @param {Function} options.compareKeys Initialize this BST's compareKeys
*/
constructor (options) {
this.tree = new _AVLTree(options)
}
checkIsAVLT () { this.tree.checkIsAVLT() }
// Insert in the internal tree, update the pointer to the root if needed
insert (key, value) {
const newTree = this.tree.insert(key, value)
// If newTree is undefined, that means its structure was not modified
if (newTree) { this.tree = newTree }
}
// Delete a value
delete (key, value) {
const newTree = this.tree.delete(key, value)
// If newTree is undefined, that means its structure was not modified
if (newTree) { this.tree = newTree }
}
}
class _AVLTree extends BinarySearchTree {
/**
* Constructor of the internal AVLTree
* @param {Object} options Optional
* @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not
* @param {Key} options.key Initialize this BST's key with key
* @param {Value} options.value Initialize this BST's data with [value]
* @param {Function} options.compareKeys Initialize this BST's compareKeys
*/
constructor (options) {
super()
options = options || {}
this.left = null
this.right = null
this.parent = options.parent !== undefined ? options.parent : null
if (Object.prototype.hasOwnProperty.call(options, 'key')) this.key = options.key
this.data = Object.prototype.hasOwnProperty.call(options, 'value') ? [options.value] : []
this.unique = options.unique || false
this.compareKeys = options.compareKeys || customUtils.defaultCompareKeysFunction
this.checkValueEquality = options.checkValueEquality || customUtils.defaultCheckValueEquality
}
/**
* Check the recorded height is correct for every node
* Throws if one height doesn't match
*/
checkHeightCorrect () {
if (!Object.prototype.hasOwnProperty.call(this, 'key')) { return } // Empty tree
if (this.left && this.left.height === undefined) { throw new Error('Undefined height for node ' + this.left.key) }
if (this.right && this.right.height === undefined) { throw new Error('Undefined height for node ' + this.right.key) }
if (this.height === undefined) { throw new Error('Undefined height for node ' + this.key) }
const leftH = this.left ? this.left.height : 0
const rightH = this.right ? this.right.height : 0
if (this.height !== 1 + Math.max(leftH, rightH)) { throw new Error('Height constraint failed for node ' + this.key) }
if (this.left) { this.left.checkHeightCorrect() }
if (this.right) { this.right.checkHeightCorrect() }
}
/**
* Return the balance factor
*/
balanceFactor () {
const leftH = this.left ? this.left.height : 0
const rightH = this.right ? this.right.height : 0
return leftH - rightH
}
/**
* Check that the balance factors are all between -1 and 1
*/
checkBalanceFactors () {
if (Math.abs(this.balanceFactor()) > 1) { throw new Error('Tree is unbalanced at node ' + this.key) }
if (this.left) { this.left.checkBalanceFactors() }
if (this.right) { this.right.checkBalanceFactors() }
}
/**
* When checking if the BST conditions are met, also check that the heights are correct
* and the tree is balanced
*/
checkIsAVLT () {
super.checkIsBST()
this.checkHeightCorrect()
this.checkBalanceFactors()
}
/**
* Perform a right rotation of the tree if possible
* and return the root of the resulting tree
* The resulting tree's nodes' heights are also updated
*/
rightRotation () {
const q = this
const p = this.left
if (!p) return q // No change
const b = p.right
// Alter tree structure
if (q.parent) {
p.parent = q.parent
if (q.parent.left === q) q.parent.left = p
else q.parent.right = p
} else {
p.parent = null
}
p.right = q
q.parent = p
q.left = b
if (b) { b.parent = q }
// Update heights
const ah = p.left ? p.left.height : 0
const bh = b ? b.height : 0
const ch = q.right ? q.right.height : 0
q.height = Math.max(bh, ch) + 1
p.height = Math.max(ah, q.height) + 1
return p
}
/**
* Perform a left rotation of the tree if possible
* and return the root of the resulting tree
* The resulting tree's nodes' heights are also updated
*/
leftRotation () {
const p = this
const q = this.right
if (!q) { return this } // No change
const b = q.left
// Alter tree structure
if (p.parent) {
q.parent = p.parent
if (p.parent.left === p) p.parent.left = q
else p.parent.right = q
} else {
q.parent = null
}
q.left = p
p.parent = q
p.right = b
if (b) { b.parent = p }
// Update heights
const ah = p.left ? p.left.height : 0
const bh = b ? b.height : 0
const ch = q.right ? q.right.height : 0
p.height = Math.max(ah, bh) + 1
q.height = Math.max(ch, p.height) + 1
return q
}
/**
* Modify the tree if its right subtree is too small compared to the left
* Return the new root if any
*/
rightTooSmall () {
if (this.balanceFactor() <= 1) return this // Right is not too small, don't change
if (this.left.balanceFactor() < 0) this.left.leftRotation()
return this.rightRotation()
}
/**
* Modify the tree if its left subtree is too small compared to the right
* Return the new root if any
*/
leftTooSmall () {
if (this.balanceFactor() >= -1) { return this } // Left is not too small, don't change
if (this.right.balanceFactor() > 0) this.right.rightRotation()
return this.leftRotation()
}
/**
* Rebalance the tree along the given path. The path is given reversed (as he was calculated
* in the insert and delete functions).
* Returns the new root of the tree
* Of course, the first element of the path must be the root of the tree
*/
rebalanceAlongPath (path) {
let newRoot = this
let rotated
let i
if (!Object.prototype.hasOwnProperty.call(this, 'key')) {
delete this.height
return this
} // Empty tree
// Rebalance the tree and update all heights
for (i = path.length - 1; i >= 0; i -= 1) {
path[i].height = 1 + Math.max(path[i].left ? path[i].left.height : 0, path[i].right ? path[i].right.height : 0)
if (path[i].balanceFactor() > 1) {
rotated = path[i].rightTooSmall()
if (i === 0) newRoot = rotated
}
if (path[i].balanceFactor() < -1) {
rotated = path[i].leftTooSmall()
if (i === 0) newRoot = rotated
}
}
return newRoot
}
/**
* Insert a key, value pair in the tree while maintaining the AVL tree height constraint
* Return a pointer to the root node, which may have changed
*/
insert (key, value) {
const insertPath = []
let currentNode = this
// Empty tree, insert as root
if (!Object.prototype.hasOwnProperty.call(this, 'key')) {
this.key = key
this.data.push(value)
this.height = 1
return this
}
// Insert new leaf at the right place
while (true) {
// Same key: no change in the tree structure
if (currentNode.compareKeys(currentNode.key, key) === 0) {
if (currentNode.unique) {
const err = new Error(`Can't insert key ${JSON.stringify(key)}, it violates the unique constraint`)
err.key = key
err.errorType = 'uniqueViolated'
throw err
} else currentNode.data.push(value)
return this
}
insertPath.push(currentNode)
if (currentNode.compareKeys(key, currentNode.key) < 0) {
if (!currentNode.left) {
insertPath.push(currentNode.createLeftChild({ key: key, value: value }))
break
} else currentNode = currentNode.left
} else {
if (!currentNode.right) {
insertPath.push(currentNode.createRightChild({ key: key, value: value }))
break
} else currentNode = currentNode.right
}
}
return this.rebalanceAlongPath(insertPath)
}
/**
* Delete a key or just a value and return the new root of the tree
* @param {Key} key
* @param {Value} value Optional. If not set, the whole key is deleted. If set, only this value is deleted
*/
delete (key, value) {
const newData = []
let replaceWith
let currentNode = this
const deletePath = []
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return this // Empty tree
// Either no match is found and the function will return from within the loop
// Or a match is found and deletePath will contain the path from the root to the node to delete after the loop
while (true) {
if (currentNode.compareKeys(key, currentNode.key) === 0) { break }
deletePath.push(currentNode)
if (currentNode.compareKeys(key, currentNode.key) < 0) {
if (currentNode.left) {
currentNode = currentNode.left
} else return this // Key not found, no modification
} else {
// currentNode.compareKeys(key, currentNode.key) is > 0
if (currentNode.right) {
currentNode = currentNode.right
} else return this // Key not found, no modification
}
}
// Delete only a value (no tree modification)
if (currentNode.data.length > 1 && value !== undefined) {
currentNode.data.forEach(function (d) {
if (!currentNode.checkValueEquality(d, value)) newData.push(d)
})
currentNode.data = newData
return this
}
// Delete a whole node
// Leaf
if (!currentNode.left && !currentNode.right) {
if (currentNode === this) { // This leaf is also the root
delete currentNode.key
currentNode.data = []
delete currentNode.height
return this
} else {
if (currentNode.parent.left === currentNode) currentNode.parent.left = null
else currentNode.parent.right = null
return this.rebalanceAlongPath(deletePath)
}
}
// Node with only one child
if (!currentNode.left || !currentNode.right) {
replaceWith = currentNode.left ? currentNode.left : currentNode.right
if (currentNode === this) { // This node is also the root
replaceWith.parent = null
return replaceWith // height of replaceWith is necessarily 1 because the tree was balanced before deletion
} else {
if (currentNode.parent.left === currentNode) {
currentNode.parent.left = replaceWith
replaceWith.parent = currentNode.parent
} else {
currentNode.parent.right = replaceWith
replaceWith.parent = currentNode.parent
}
return this.rebalanceAlongPath(deletePath)
}
}
// Node with two children
// Use the in-order predecessor (no need to randomize since we actively rebalance)
deletePath.push(currentNode)
replaceWith = currentNode.left
// Special case: the in-order predecessor is right below the node to delete
if (!replaceWith.right) {
currentNode.key = replaceWith.key
currentNode.data = replaceWith.data
currentNode.left = replaceWith.left
if (replaceWith.left) { replaceWith.left.parent = currentNode }
return this.rebalanceAlongPath(deletePath)
}
// After this loop, replaceWith is the right-most leaf in the left subtree
// and deletePath the path from the root (inclusive) to replaceWith (exclusive)
while (true) {
if (replaceWith.right) {
deletePath.push(replaceWith)
replaceWith = replaceWith.right
} else break
}
currentNode.key = replaceWith.key
currentNode.data = replaceWith.data
replaceWith.parent.right = replaceWith.left
if (replaceWith.left) replaceWith.left.parent = replaceWith.parent
return this.rebalanceAlongPath(deletePath)
}
}
/**
* Keep a pointer to the internal tree constructor for testing purposes
*/
AVLTree._AVLTree = _AVLTree;
/**
* Other functions we want to use on an AVLTree as if it were the internal _AVLTree
*/
['getNumberOfKeys', 'search', 'betweenBounds', 'prettyPrint', 'executeOnEveryNode'].forEach(function (fn) {
AVLTree.prototype[fn] = function () {
return this.tree[fn].apply(this.tree, arguments)
}
})
// Interface
module.exports = AVLTree

452
node_modules/@seald-io/binary-search-tree/lib/bst.js generated vendored Normal file
View File

@ -0,0 +1,452 @@
/**
* Simple binary search tree
*/
const customUtils = require('./customUtils')
class BinarySearchTree {
/**
* Constructor
* @param {Object} options Optional
* @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not
* @param {Key} options.key Initialize this BST's key with key
* @param {Value} options.value Initialize this BST's data with [value]
* @param {Function} options.compareKeys Initialize this BST's compareKeys
*/
constructor (options) {
options = options || {}
this.left = null
this.right = null
this.parent = options.parent !== undefined ? options.parent : null
if (Object.prototype.hasOwnProperty.call(options, 'key')) { this.key = options.key }
this.data = Object.prototype.hasOwnProperty.call(options, 'value') ? [options.value] : []
this.unique = options.unique || false
this.compareKeys = options.compareKeys || customUtils.defaultCompareKeysFunction
this.checkValueEquality = options.checkValueEquality || customUtils.defaultCheckValueEquality
}
/**
* Get the descendant with max key
*/
getMaxKeyDescendant () {
if (this.right) return this.right.getMaxKeyDescendant()
else return this
}
/**
* Get the maximum key
*/
getMaxKey () {
return this.getMaxKeyDescendant().key
}
/**
* Get the descendant with min key
*/
getMinKeyDescendant () {
if (this.left) return this.left.getMinKeyDescendant()
else return this
}
/**
* Get the minimum key
*/
getMinKey () {
return this.getMinKeyDescendant().key
}
/**
* Check that all nodes (incl. leaves) fullfil condition given by fn
* test is a function passed every (key, data) and which throws if the condition is not met
*/
checkAllNodesFullfillCondition (test) {
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return
test(this.key, this.data)
if (this.left) this.left.checkAllNodesFullfillCondition(test)
if (this.right) this.right.checkAllNodesFullfillCondition(test)
}
/**
* Check that the core BST properties on node ordering are verified
* Throw if they aren't
*/
checkNodeOrdering () {
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return
if (this.left) {
this.left.checkAllNodesFullfillCondition(k => {
if (this.compareKeys(k, this.key) >= 0) throw new Error(`Tree with root ${this.key} is not a binary search tree`)
})
this.left.checkNodeOrdering()
}
if (this.right) {
this.right.checkAllNodesFullfillCondition(k => {
if (this.compareKeys(k, this.key) <= 0) throw new Error(`Tree with root ${this.key} is not a binary search tree`)
})
this.right.checkNodeOrdering()
}
}
/**
* Check that all pointers are coherent in this tree
*/
checkInternalPointers () {
if (this.left) {
if (this.left.parent !== this) throw new Error(`Parent pointer broken for key ${this.key}`)
this.left.checkInternalPointers()
}
if (this.right) {
if (this.right.parent !== this) throw new Error(`Parent pointer broken for key ${this.key}`)
this.right.checkInternalPointers()
}
}
/**
* Check that a tree is a BST as defined here (node ordering and pointer references)
*/
checkIsBST () {
this.checkNodeOrdering()
this.checkInternalPointers()
if (this.parent) throw new Error("The root shouldn't have a parent")
}
/**
* Get number of keys inserted
*/
getNumberOfKeys () {
let res
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return 0
res = 1
if (this.left) res += this.left.getNumberOfKeys()
if (this.right) res += this.right.getNumberOfKeys()
return res
}
/**
* Create a BST similar (i.e. same options except for key and value) to the current one
* Use the same constructor (i.e. BinarySearchTree, AVLTree etc)
* @param {Object} options see constructor
*/
createSimilar (options) {
options = options || {}
options.unique = this.unique
options.compareKeys = this.compareKeys
options.checkValueEquality = this.checkValueEquality
return new this.constructor(options)
}
/**
* Create the left child of this BST and return it
*/
createLeftChild (options) {
const leftChild = this.createSimilar(options)
leftChild.parent = this
this.left = leftChild
return leftChild
}
/**
* Create the right child of this BST and return it
*/
createRightChild (options) {
const rightChild = this.createSimilar(options)
rightChild.parent = this
this.right = rightChild
return rightChild
}
/**
* Insert a new element
*/
insert (key, value) {
// Empty tree, insert as root
if (!Object.prototype.hasOwnProperty.call(this, 'key')) {
this.key = key
this.data.push(value)
return
}
// Same key as root
if (this.compareKeys(this.key, key) === 0) {
if (this.unique) {
const err = new Error(`Can't insert key ${JSON.stringify(key)}, it violates the unique constraint`)
err.key = key
err.errorType = 'uniqueViolated'
throw err
} else this.data.push(value)
return
}
if (this.compareKeys(key, this.key) < 0) {
// Insert in left subtree
if (this.left) this.left.insert(key, value)
else this.createLeftChild({ key: key, value: value })
} else {
// Insert in right subtree
if (this.right) this.right.insert(key, value)
else this.createRightChild({ key: key, value: value })
}
}
/**
* Search for all data corresponding to a key
*/
search (key) {
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return []
if (this.compareKeys(this.key, key) === 0) return this.data
if (this.compareKeys(key, this.key) < 0) {
if (this.left) return this.left.search(key)
else return []
} else {
if (this.right) return this.right.search(key)
else return []
}
}
/**
* Return a function that tells whether a given key matches a lower bound
*/
getLowerBoundMatcher (query) {
// No lower bound
if (!Object.prototype.hasOwnProperty.call(query, '$gt') && !Object.prototype.hasOwnProperty.call(query, '$gte')) return () => true
if (Object.prototype.hasOwnProperty.call(query, '$gt') && Object.prototype.hasOwnProperty.call(query, '$gte')) {
if (this.compareKeys(query.$gte, query.$gt) === 0) return key => this.compareKeys(key, query.$gt) > 0
if (this.compareKeys(query.$gte, query.$gt) > 0) return key => this.compareKeys(key, query.$gte) >= 0
else return key => this.compareKeys(key, query.$gt) > 0
}
if (Object.prototype.hasOwnProperty.call(query, '$gt')) return key => this.compareKeys(key, query.$gt) > 0
else return key => this.compareKeys(key, query.$gte) >= 0
}
/**
* Return a function that tells whether a given key matches an upper bound
*/
getUpperBoundMatcher (query) {
// No lower bound
if (!Object.prototype.hasOwnProperty.call(query, '$lt') && !Object.prototype.hasOwnProperty.call(query, '$lte')) return () => true
if (Object.prototype.hasOwnProperty.call(query, '$lt') && Object.prototype.hasOwnProperty.call(query, '$lte')) {
if (this.compareKeys(query.$lte, query.$lt) === 0) return key => this.compareKeys(key, query.$lt) < 0
if (this.compareKeys(query.$lte, query.$lt) < 0) return key => this.compareKeys(key, query.$lte) <= 0
else return key => this.compareKeys(key, query.$lt) < 0
}
if (Object.prototype.hasOwnProperty.call(query, '$lt')) return key => this.compareKeys(key, query.$lt) < 0
else return key => this.compareKeys(key, query.$lte) <= 0
}
/**
* Get all data for a key between bounds
* Return it in key order
* @param {Object} query Mongo-style query where keys are $lt, $lte, $gt or $gte (other keys are not considered)
* @param {Functions} lbm/ubm matching functions calculated at the first recursive step
*/
betweenBounds (query, lbm, ubm) {
const res = []
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return [] // Empty tree
lbm = lbm || this.getLowerBoundMatcher(query)
ubm = ubm || this.getUpperBoundMatcher(query)
if (lbm(this.key) && this.left) append(res, this.left.betweenBounds(query, lbm, ubm))
if (lbm(this.key) && ubm(this.key)) append(res, this.data)
if (ubm(this.key) && this.right) append(res, this.right.betweenBounds(query, lbm, ubm))
return res
}
/**
* Delete the current node if it is a leaf
* Return true if it was deleted
*/
deleteIfLeaf () {
if (this.left || this.right) return false
// The leaf is itself a root
if (!this.parent) {
delete this.key
this.data = []
return true
}
if (this.parent.left === this) this.parent.left = null
else this.parent.right = null
return true
}
/**
* Delete the current node if it has only one child
* Return true if it was deleted
*/
deleteIfOnlyOneChild () {
let child
if (this.left && !this.right) child = this.left
if (!this.left && this.right) child = this.right
if (!child) return false
// Root
if (!this.parent) {
this.key = child.key
this.data = child.data
this.left = null
if (child.left) {
this.left = child.left
child.left.parent = this
}
this.right = null
if (child.right) {
this.right = child.right
child.right.parent = this
}
return true
}
if (this.parent.left === this) {
this.parent.left = child
child.parent = this.parent
} else {
this.parent.right = child
child.parent = this.parent
}
return true
}
/**
* Delete a key or just a value
* @param {Key} key
* @param {Value} value Optional. If not set, the whole key is deleted. If set, only this value is deleted
*/
delete (key, value) {
const newData = []
let replaceWith
if (!Object.prototype.hasOwnProperty.call(this, 'key')) return
if (this.compareKeys(key, this.key) < 0) {
if (this.left) this.left.delete(key, value)
return
}
if (this.compareKeys(key, this.key) > 0) {
if (this.right) this.right.delete(key, value)
return
}
if (!this.compareKeys(key, this.key) === 0) return
// Delete only a value
if (this.data.length > 1 && value !== undefined) {
this.data.forEach(d => {
if (!this.checkValueEquality(d, value)) newData.push(d)
})
this.data = newData
return
}
// Delete the whole node
if (this.deleteIfLeaf()) return
if (this.deleteIfOnlyOneChild()) return
// We are in the case where the node to delete has two children
if (Math.random() >= 0.5) { // Randomize replacement to avoid unbalancing the tree too much
// Use the in-order predecessor
replaceWith = this.left.getMaxKeyDescendant()
this.key = replaceWith.key
this.data = replaceWith.data
if (this === replaceWith.parent) { // Special case
this.left = replaceWith.left
if (replaceWith.left) replaceWith.left.parent = replaceWith.parent
} else {
replaceWith.parent.right = replaceWith.left
if (replaceWith.left) replaceWith.left.parent = replaceWith.parent
}
} else {
// Use the in-order successor
replaceWith = this.right.getMinKeyDescendant()
this.key = replaceWith.key
this.data = replaceWith.data
if (this === replaceWith.parent) { // Special case
this.right = replaceWith.right
if (replaceWith.right) replaceWith.right.parent = replaceWith.parent
} else {
replaceWith.parent.left = replaceWith.right
if (replaceWith.right) replaceWith.right.parent = replaceWith.parent
}
}
}
/**
* Execute a function on every node of the tree, in key order
* @param {Function} fn Signature: node. Most useful will probably be node.key and node.data
*/
executeOnEveryNode (fn) {
if (this.left) this.left.executeOnEveryNode(fn)
fn(this)
if (this.right) this.right.executeOnEveryNode(fn)
}
/**
* Pretty print a tree
* @param {Boolean} printData To print the nodes' data along with the key
*/
prettyPrint (printData, spacing) {
spacing = spacing || ''
console.log(`${spacing}* ${this.key}`)
if (printData) console.log(`${spacing}* ${this.data}`)
if (!this.left && !this.right) return
if (this.left) this.left.prettyPrint(printData, `${spacing} `)
else console.log(`${spacing} *`)
if (this.right) this.right.prettyPrint(printData, `${spacing} `)
else console.log(`${spacing} *`)
}
}
// ================================
// Methods used to test the tree
// ================================
// ============================================
// Methods used to actually work on the tree
// ============================================
// Append all elements in toAppend to array
function append (array, toAppend) {
for (let i = 0; i < toAppend.length; i += 1) {
array.push(toAppend[i])
}
}
// Interface
module.exports = BinarySearchTree

View File

@ -0,0 +1,38 @@
/**
* Return an array with the numbers from 0 to n-1, in a random order
*/
const getRandomArray = n => {
if (n === 0) return []
if (n === 1) return [0]
const res = getRandomArray(n - 1)
const next = Math.floor(Math.random() * n)
res.splice(next, 0, n - 1) // Add n-1 at a random position in the array
return res
}
module.exports.getRandomArray = getRandomArray
/*
* Default compareKeys function will work for numbers, strings and dates
*/
const defaultCompareKeysFunction = (a, b) => {
if (a < b) return -1
if (a > b) return 1
if (a === b) return 0
const err = new Error("Couldn't compare elements")
err.a = a
err.b = b
throw err
}
module.exports.defaultCompareKeysFunction = defaultCompareKeysFunction
/**
* Check whether two values are equal (used in non-unique deletion)
*/
const defaultCheckValueEquality = (a, b) => a === b
module.exports.defaultCheckValueEquality = defaultCheckValueEquality

51
node_modules/@seald-io/binary-search-tree/package.json generated vendored Normal file
View File

@ -0,0 +1,51 @@
{
"name": "@seald-io/binary-search-tree",
"version": "1.0.3",
"author": {
"name": "Timothée Rebours",
"email": "tim@seald.io",
"url": "https://www.seald.io/"
},
"files": [
"lib/**/*.js",
"index.js"
],
"contributors": [
{
"name": "Louis Chatriot",
"email": "louis.chatriot@gmail.com"
},
{
"name": "Timothée Rebours",
"email": "tim@seald.io",
"url": "https://www.seald.io/"
}
],
"description": "Different binary search tree implementations, including a self-balancing one (AVL)",
"keywords": [
"AVL tree",
"binary search tree",
"self-balancing",
"AVL tree"
],
"homepage": "https://github.com/seald/node-binary-search-tree",
"repository": {
"type": "git",
"url": "git@github.com:seald/node-binary-search-tree.git"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^8.4.0",
"semver": "^7.3.5",
"standard": "^16.0.3"
},
"scripts": {
"test": "mocha --reporter spec --timeout 10000",
"lint": "standard"
},
"main": "index.js",
"licence": "MIT",
"publishConfig": {
"access": "public"
}
}