'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] }