Initial import with skill sheet working
This commit is contained in:
64
node_modules/@seald-io/binary-search-tree/CHANGELOG.md
generated
vendored
Normal file
64
node_modules/@seald-io/binary-search-tree/CHANGELOG.md
generated
vendored
Normal 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
19
node_modules/@seald-io/binary-search-tree/LICENSE.md
generated
vendored
Normal 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
130
node_modules/@seald-io/binary-search-tree/README.md
generated
vendored
Normal 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
2
node_modules/@seald-io/binary-search-tree/index.js
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
module.exports.BinarySearchTree = require('./lib/bst')
|
||||
module.exports.AVLTree = require('./lib/avltree')
|
412
node_modules/@seald-io/binary-search-tree/lib/avltree.js
generated
vendored
Normal file
412
node_modules/@seald-io/binary-search-tree/lib/avltree.js
generated
vendored
Normal 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
452
node_modules/@seald-io/binary-search-tree/lib/bst.js
generated
vendored
Normal 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
|
38
node_modules/@seald-io/binary-search-tree/lib/customUtils.js
generated
vendored
Normal file
38
node_modules/@seald-io/binary-search-tree/lib/customUtils.js
generated
vendored
Normal 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
51
node_modules/@seald-io/binary-search-tree/package.json
generated
vendored
Normal 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"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user