-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnumber-allocator.js
259 lines (238 loc) · 7.11 KB
/
number-allocator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright Takatoshi Kondo 2021
//
// Distributed under the MIT License
'use strict'
const SortedSet = require('collections/sorted-set')
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()
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.length === 0) return null
return this.ss.min().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.length === 0) {
debugTrace('alloc():empty')
return null
}
const it = this.ss.min()
const num = it.low
if (num + 1 <= it.high) {
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
// x|----|
++it.low
} else {
this.ss.shift()
}
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.findLeastGreaterThanOrEqual(key)
if (it) {
if (it.value.equals(key)) {
// |x|
this.ss.delete(it.value)
debugTrace('use():' + num)
return true
}
// x |-----|
if (it.value.low > num) return false
// |x----|
if (it.value.low === num) {
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
// x|----|
++it.value.low
debugTrace('use():' + num)
return true
}
// |----x|
if (it.value.high === num) {
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
// |----|x
--it.value.high
debugTrace('use():' + num)
return true
}
const low = it.value.low
// |--x--|
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
// x|--|
it.value.low = num + 1
// |--|x|--|
this.ss.push(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.findLeastGreaterThanOrEqual(key)
if (it) {
if (it.value.low <= num && num <= it.value.high) {
debugError('free():' + num + ' has already been vacant')
return
}
if (it === this.ss.findLeast()) {
// v....
if (num + 1 === it.value.low) {
// Concat to right
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
--it.value.low
} else {
// Insert new interval
this.ss.push(key)
}
} else {
// ..v..
const itl = this.ss.findGreatestLessThan(key)
if (itl.value.high + 1 === num) {
if (num + 1 === it.value.low) {
// Concat to left and right
this.ss.delete(itl.value)
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
it.value.low = itl.value.low
} else {
// Concat to left
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
itl.value.high = num
}
} else {
if (num + 1 === it.value.low) {
// Concat to right
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
it.value.low = num
} else {
// Insert new interval
this.ss.push(key)
}
}
}
} else {
// ....v
if (it === this.ss.findLeast()) {
// Insert new interval
this.ss.push(key)
return
}
const itl = this.ss.findGreatestLessThan(key)
if (itl.value.high + 1 === num) {
// Concat to left
// Overwrite the interval in the ss but it is safe,
// because no order violation is happened.
itl.value.high = num
} else {
// Insert new interval
this.ss.push(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.push(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.length
}
/**
* 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.length)
const it = this.ss.iterator()
let v = it.next()
while (v.value) {
console.log(v.value)
v = it.next()
}
}
module.exports = NumberAllocator