Created
August 3, 2018 15:09
-
-
Save zerobias/7634513d727adeacf4658524c5903ea4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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) | |
} | |
} | |
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] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment