IOTConnect-Web/node_modules/number-allocator/lib/number-allocator.js
2024-05-09 09:49:52 +08:00

250 lines
6.7 KiB
JavaScript

// Copyright Takatoshi Kondo 2021
//
// Distributed under the MIT License
'use strict'
const SortedSet = require('js-sdsl').OrderedSet
const debugTrace = require('debug')('number-allocator:trace')
const debugError = require('debug')('number-allocator:error')
/**
* Interval constructor
* @constructor
* @param {Number} low - The lowest value of the interval
* @param {Number} high - The highest value of the interval
*/
function Interval (low, high) {
this.low = low
this.high = high
}
Interval.prototype.equals = function (other) {
return this.low === other.low && this.high === other.high
}
Interval.prototype.compare = function (other) {
if (this.low < other.low && this.high < other.low) return -1
if (other.low < this.low && other.high < this.low) return 1
return 0
}
/**
* NumberAllocator constructor.
* The all numbers are set to vacant status.
* Time Complexity O(1)
* @constructor
* @param {Number} min - The maximum number of allocatable. The number must be integer.
* @param {Number} maxh - The minimum number of allocatable. The number must be integer.
*/
function NumberAllocator (min, max) {
if (!(this instanceof NumberAllocator)) {
return new NumberAllocator(min, max)
}
this.min = min
this.max = max
this.ss = new SortedSet(
[],
(lhs, rhs) => {
return lhs.compare(rhs)
}
)
debugTrace('Create')
this.clear()
}
/**
* Get the first vacant number. The status of the number is not updated.
* Time Complexity O(1)
* @return {Number} - The first vacant number. If all numbers are occupied, return null.
* When alloc() is called then the same value will be allocated.
*/
NumberAllocator.prototype.firstVacant = function () {
if (this.ss.size() === 0) return null
return this.ss.front().low
}
/**
* Allocate the first vacant number. The number become occupied status.
* Time Complexity O(1)
* @return {Number} - The first vacant number. If all numbers are occupied, return null.
*/
NumberAllocator.prototype.alloc = function () {
if (this.ss.size() === 0) {
debugTrace('alloc():empty')
return null
}
const it = this.ss.begin()
const low = it.pointer.low
const high = it.pointer.high
const num = low
if (num + 1 <= high) {
// x|----|
this.ss.updateKeyByIterator(it, new Interval(low + 1, high))
} else {
this.ss.eraseElementByPos(0)
}
debugTrace('alloc():' + num)
return num
}
/**
* Use the number. The number become occupied status.
* If the number has already been occupied, then return false.
* Time Complexity O(logN) : N is the number of intervals (not numbers)
* @param {Number} num - The number to request use.
* @return {Boolean} - If `num` was not occupied, then return true, otherwise return false.
*/
NumberAllocator.prototype.use = function (num) {
const key = new Interval(num, num)
const it = this.ss.lowerBound(key)
if (!it.equals(this.ss.end())) {
const low = it.pointer.low
const high = it.pointer.high
if (it.pointer.equals(key)) {
// |x|
this.ss.eraseElementByIterator(it)
debugTrace('use():' + num)
return true
}
// x |-----|
if (low > num) return false
// |x----|
if (low === num) {
// x|----|
this.ss.updateKeyByIterator(it, new Interval(low + 1, high))
debugTrace('use():' + num)
return true
}
// |----x|
if (high === num) {
// |----|x
this.ss.updateKeyByIterator(it, new Interval(low, high - 1))
debugTrace('use():' + num)
return true
}
// |--x--|
// x|--|
this.ss.updateKeyByIterator(it, new Interval(num + 1, high))
// |--|x|--|
this.ss.insert(new Interval(low, num - 1))
debugTrace('use():' + num)
return true
}
debugTrace('use():failed')
return false
}
/**
* Deallocate the number. The number become vacant status.
* Time Complexity O(logN) : N is the number of intervals (not numbers)
* @param {Number} num - The number to deallocate. The number must be occupied status.
* In other words, the number must be allocated by alloc() or occupied be use().
*/
NumberAllocator.prototype.free = function (num) {
if (num < this.min || num > this.max) {
debugError('free():' + num + ' is out of range')
return
}
const key = new Interval(num, num)
const it = this.ss.upperBound(key)
if (it.equals(this.ss.end())) {
// ....v
if (it.equals(this.ss.begin())) {
// Insert new interval
this.ss.insert(key)
return
}
it.pre()
const low = it.pointer.high
const high = it.pointer.high
if (high + 1 === num) {
// Concat to left
this.ss.updateKeyByIterator(it, new Interval(low, num))
} else {
// Insert new interval
this.ss.insert(key)
}
} else {
if (it.equals(this.ss.begin())) {
// v....
if (num + 1 === it.pointer.low) {
// Concat to right
const high = it.pointer.high
this.ss.updateKeyByIterator(it, new Interval(num, high))
} else {
// Insert new interval
this.ss.insert(key)
}
} else {
// ..v..
const rLow = it.pointer.low
const rHigh = it.pointer.high
it.pre()
const lLow = it.pointer.low
const lHigh = it.pointer.high
if (lHigh + 1 === num) {
if (num + 1 === rLow) {
// Concat to left and right
this.ss.eraseElementByIterator(it)
this.ss.updateKeyByIterator(it, new Interval(lLow, rHigh))
} else {
// Concat to left
this.ss.updateKeyByIterator(it, new Interval(lLow, num))
}
} else {
if (num + 1 === rLow) {
// Concat to right
this.ss.eraseElementByIterator(it.next())
this.ss.insert(new Interval(num, rHigh))
} else {
// Insert new interval
this.ss.insert(key)
}
}
}
}
debugTrace('free():' + num)
}
/**
* Clear all occupied numbers.
* The all numbers are set to vacant status.
* Time Complexity O(1)
*/
NumberAllocator.prototype.clear = function () {
debugTrace('clear()')
this.ss.clear()
this.ss.insert(new Interval(this.min, this.max))
}
/**
* Get the number of intervals. Interval is internal structure of this library.
* This function is for debugging.
* Time Complexity O(1)
* @return {Number} - The number of intervals.
*/
NumberAllocator.prototype.intervalCount = function () {
return this.ss.size()
}
/**
* Dump the internal structor of the library.
* This function is for debugging.
* Time Complexity O(N) : N is the number of intervals (not numbers)
*/
NumberAllocator.prototype.dump = function () {
console.log('length:' + this.ss.size())
for (const element of this.ss) {
console.log(element)
}
}
module.exports = NumberAllocator