250 lines
6.0 KiB
JavaScript
250 lines
6.0 KiB
JavaScript
|
|
'use strict'
|
||
|
|
|
||
|
|
// JS treats subjects of bitwise operators as SIGNED 32 bit numbers,
|
||
|
|
// which means the maximum amount of bits we can store inside each byte
|
||
|
|
// is 7..
|
||
|
|
const BITS_PER_BYTE = 7
|
||
|
|
|
||
|
|
module.exports = class SparseArray {
|
||
|
|
constructor () {
|
||
|
|
this._bitArrays = []
|
||
|
|
this._data = []
|
||
|
|
this._length = 0
|
||
|
|
this._changedLength = false
|
||
|
|
this._changedData = false
|
||
|
|
}
|
||
|
|
|
||
|
|
set (index, value) {
|
||
|
|
let pos = this._internalPositionFor(index, false)
|
||
|
|
if (value === undefined) {
|
||
|
|
// unsetting
|
||
|
|
if (pos !== -1) {
|
||
|
|
// remove item from bit array and array itself
|
||
|
|
this._unsetInternalPos(pos)
|
||
|
|
this._unsetBit(index)
|
||
|
|
this._changedLength = true
|
||
|
|
this._changedData = true
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
let needsSort = false
|
||
|
|
if (pos === -1) {
|
||
|
|
pos = this._data.length
|
||
|
|
this._setBit(index)
|
||
|
|
this._changedData = true
|
||
|
|
} else {
|
||
|
|
needsSort = true
|
||
|
|
}
|
||
|
|
this._setInternalPos(pos, index, value, needsSort)
|
||
|
|
this._changedLength = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
unset (index) {
|
||
|
|
this.set(index, undefined)
|
||
|
|
}
|
||
|
|
|
||
|
|
get (index) {
|
||
|
|
this._sortData()
|
||
|
|
const pos = this._internalPositionFor(index, true)
|
||
|
|
if (pos === -1) {
|
||
|
|
return undefined
|
||
|
|
}
|
||
|
|
return this._data[pos][1]
|
||
|
|
}
|
||
|
|
|
||
|
|
push (value) {
|
||
|
|
this.set(this.length, value)
|
||
|
|
return this.length
|
||
|
|
}
|
||
|
|
|
||
|
|
get length () {
|
||
|
|
this._sortData()
|
||
|
|
if (this._changedLength) {
|
||
|
|
const last = this._data[this._data.length - 1]
|
||
|
|
this._length = last ? last[0] + 1 : 0
|
||
|
|
this._changedLength = false
|
||
|
|
}
|
||
|
|
return this._length
|
||
|
|
}
|
||
|
|
|
||
|
|
forEach (iterator) {
|
||
|
|
let i = 0
|
||
|
|
while(i < this.length) {
|
||
|
|
iterator(this.get(i), i, this)
|
||
|
|
i++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
map (iterator) {
|
||
|
|
let i = 0
|
||
|
|
let mapped = new Array(this.length)
|
||
|
|
while(i < this.length) {
|
||
|
|
mapped[i] = iterator(this.get(i), i, this)
|
||
|
|
i++
|
||
|
|
}
|
||
|
|
return mapped
|
||
|
|
}
|
||
|
|
|
||
|
|
reduce (reducer, initialValue) {
|
||
|
|
let i = 0
|
||
|
|
let acc = initialValue
|
||
|
|
while(i < this.length) {
|
||
|
|
const value = this.get(i)
|
||
|
|
acc = reducer(acc, value, i)
|
||
|
|
i++
|
||
|
|
}
|
||
|
|
return acc
|
||
|
|
}
|
||
|
|
|
||
|
|
find (finder) {
|
||
|
|
let i = 0, found, last
|
||
|
|
while ((i < this.length) && !found) {
|
||
|
|
last = this.get(i)
|
||
|
|
found = finder(last)
|
||
|
|
i++
|
||
|
|
}
|
||
|
|
return found ? last : undefined
|
||
|
|
}
|
||
|
|
|
||
|
|
_internalPositionFor (index, noCreate) {
|
||
|
|
const bytePos = this._bytePosFor(index, noCreate)
|
||
|
|
if (bytePos >= this._bitArrays.length) {
|
||
|
|
return -1
|
||
|
|
}
|
||
|
|
const byte = this._bitArrays[bytePos]
|
||
|
|
const bitPos = index - bytePos * BITS_PER_BYTE
|
||
|
|
const exists = (byte & (1 << bitPos)) > 0
|
||
|
|
if (!exists) {
|
||
|
|
return -1
|
||
|
|
}
|
||
|
|
const previousPopCount = this._bitArrays.slice(0, bytePos).reduce(popCountReduce, 0)
|
||
|
|
|
||
|
|
const mask = ~(0xffffffff << (bitPos + 1))
|
||
|
|
const bytePopCount = popCount(byte & mask)
|
||
|
|
const arrayPos = previousPopCount + bytePopCount - 1
|
||
|
|
return arrayPos
|
||
|
|
}
|
||
|
|
|
||
|
|
_bytePosFor (index, noCreate) {
|
||
|
|
const bytePos = Math.floor(index / BITS_PER_BYTE)
|
||
|
|
const targetLength = bytePos + 1
|
||
|
|
while (!noCreate && this._bitArrays.length < targetLength) {
|
||
|
|
this._bitArrays.push(0)
|
||
|
|
}
|
||
|
|
return bytePos
|
||
|
|
}
|
||
|
|
|
||
|
|
_setBit (index) {
|
||
|
|
const bytePos = this._bytePosFor(index, false)
|
||
|
|
this._bitArrays[bytePos] |= (1 << (index - (bytePos * BITS_PER_BYTE)))
|
||
|
|
}
|
||
|
|
|
||
|
|
_unsetBit(index) {
|
||
|
|
const bytePos = this._bytePosFor(index, false)
|
||
|
|
this._bitArrays[bytePos] &= ~(1 << (index - (bytePos * BITS_PER_BYTE)))
|
||
|
|
}
|
||
|
|
|
||
|
|
_setInternalPos(pos, index, value, needsSort) {
|
||
|
|
const data =this._data
|
||
|
|
const elem = [index, value]
|
||
|
|
if (needsSort) {
|
||
|
|
this._sortData()
|
||
|
|
data[pos] = elem
|
||
|
|
} else {
|
||
|
|
// new element. just shove it into the array
|
||
|
|
// but be nice about where we shove it
|
||
|
|
// in order to make sorting it later easier
|
||
|
|
if (data.length) {
|
||
|
|
if (data[data.length - 1][0] >= index) {
|
||
|
|
data.push(elem)
|
||
|
|
} else if (data[0][0] <= index) {
|
||
|
|
data.unshift(elem)
|
||
|
|
} else {
|
||
|
|
const randomIndex = Math.round(data.length / 2)
|
||
|
|
this._data = data.slice(0, randomIndex).concat(elem).concat(data.slice(randomIndex))
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this._data.push(elem)
|
||
|
|
}
|
||
|
|
this._changedData = true
|
||
|
|
this._changedLength = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_unsetInternalPos (pos) {
|
||
|
|
this._data.splice(pos, 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
_sortData () {
|
||
|
|
if (this._changedData) {
|
||
|
|
this._data.sort(sortInternal)
|
||
|
|
}
|
||
|
|
|
||
|
|
this._changedData = false
|
||
|
|
}
|
||
|
|
|
||
|
|
bitField () {
|
||
|
|
const bytes = []
|
||
|
|
let pendingBitsForResultingByte = 8
|
||
|
|
let pendingBitsForNewByte = 0
|
||
|
|
let resultingByte = 0
|
||
|
|
let newByte
|
||
|
|
const pending = this._bitArrays.slice()
|
||
|
|
while (pending.length || pendingBitsForNewByte) {
|
||
|
|
if (pendingBitsForNewByte === 0) {
|
||
|
|
newByte = pending.shift()
|
||
|
|
pendingBitsForNewByte = 7
|
||
|
|
}
|
||
|
|
|
||
|
|
const usingBits = Math.min(pendingBitsForNewByte, pendingBitsForResultingByte)
|
||
|
|
const mask = ~(0b11111111 << usingBits)
|
||
|
|
const masked = newByte & mask
|
||
|
|
resultingByte |= masked << (8 - pendingBitsForResultingByte)
|
||
|
|
newByte = newByte >>> usingBits
|
||
|
|
pendingBitsForNewByte -= usingBits
|
||
|
|
pendingBitsForResultingByte -= usingBits
|
||
|
|
|
||
|
|
if (!pendingBitsForResultingByte || (!pendingBitsForNewByte && !pending.length)) {
|
||
|
|
bytes.push(resultingByte)
|
||
|
|
resultingByte = 0
|
||
|
|
pendingBitsForResultingByte = 8
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// remove trailing zeroes
|
||
|
|
for(var i = bytes.length - 1; i > 0; i--) {
|
||
|
|
const value = bytes[i]
|
||
|
|
if (value === 0) {
|
||
|
|
bytes.pop()
|
||
|
|
} else {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return bytes
|
||
|
|
}
|
||
|
|
|
||
|
|
compactArray () {
|
||
|
|
this._sortData()
|
||
|
|
return this._data.map(valueOnly)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function popCountReduce (count, byte) {
|
||
|
|
return count + popCount(byte)
|
||
|
|
}
|
||
|
|
|
||
|
|
function popCount(_v) {
|
||
|
|
let v = _v
|
||
|
|
v = v - ((v >> 1) & 0x55555555) // reuse input as temporary
|
||
|
|
v = (v & 0x33333333) + ((v >> 2) & 0x33333333) // temp
|
||
|
|
return ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
|
||
|
|
}
|
||
|
|
|
||
|
|
function sortInternal (a, b) {
|
||
|
|
return a[0] - b[0]
|
||
|
|
}
|
||
|
|
|
||
|
|
function valueOnly (elem) {
|
||
|
|
return elem[1]
|
||
|
|
}
|