mirror of
https://github.com/JasonYANG170/IOTConnect-Web.git
synced 2024-11-23 20:26:28 +00:00
250 lines
6.7 KiB
JavaScript
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
|