Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encoding/decoding of bit array to string #1

Merged
merged 5 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/*
dist/*
79 changes: 79 additions & 0 deletions src/bitarray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,85 @@ export default class BitArray extends BitTypedArray {
return ret;
}

/**
*
* @param charArray a set of n characters to use to encode the BitArray; charArray.length must be a power of 2 (2, 4, 8, etc)
* The more characters in the set, the more compact the resulting output will be
* @returns a string encoded using the provided character set (e.g., base64 encoding can be achieved with this)
*/
encodeWithCharacterSet( charArray: string): string {
const log2 = Math.log2(charArray.length);

if (log2 < 1 || log2 % 1 !== 0) {
throw new RangeError('Provided charArray\'s length must non-0 positive power of 2');
}

const ret = [];

let val = 0;
let valLen = 0;
for (const b of this) {
valLen++;
val <<= 1;
val += b;

if (valLen === log2) {
ret.push(charArray[val]);
valLen = val = 0;
}
}

if (valLen !== 0) {
val <<= (log2 - valLen);
ret.push(charArray[val]);
}

return ret.join('');
}

/**
*
* @param charArray a set of n characters to use to encode the BitArray; charArray.length must be a power of 2 (2, 4, 8, etc),
* and should generally match the set used in the original encoding
* @param encodedString an encoded string built with encodeWithCharacterSet
* @returns a BitArray of the encodedString decoded using charArray
*/
static decodeWithCharacterSet( charArray: string, encodedString: string ): BitArray {
const log2 = Math.log2(charArray.length);

if (log2 < 1 || log2 % 1 !== 0) {
throw new RangeError('Provided charArray\'s length must non-0 positive power of 2');
}

const pad = (s: string) => '0'.repeat(log2 - s.length) + s

const charMap = {} // maps each character to its integral value
for (var i = 0; i < charArray.length; i++) {
charMap[charArray[i]] = pad(i.toString(2))
}

const deserialized = Array.from(encodedString).map(c => {
if (!(c in charMap)) {
throw new RangeError('Invalid character found in encoded string');
}
return charMap[c];
}).join('')
const ret = BitArray.from(deserialized);
return ret;
}

// Convenience specializations for encoding base64MIME and base64Url
static base64MIMEChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
encodeBase64MIME() { return this.encodeWithCharacterSet(BitArray.base64MIMEChars) }
static decodeBase64MIME(encodedString: string) {
return BitArray.decodeWithCharacterSet(BitArray.base64MIMEChars, encodedString);
}

static base64UrlChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
encodeBase64Url() { return this.encodeWithCharacterSet(BitArray.base64UrlChars) }
static decodeBase64Url(encodedString: string) {
return BitArray.decodeWithCharacterSet(BitArray.base64UrlChars, encodedString);
}
}

// create aliases
Expand Down
37 changes: 36 additions & 1 deletion test/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ const arr2 = new Array( len + 10 ).fill(false).map( x => Math.random() > 0.5 )

const sample1 = BitArray.from( arr1 );
const sample2 = BitArray.of( ...arr2 );
const sample3 = BitArray.from( '0110');

// Returns true if the block throws
function expectThrow( fn: () => void) {
try {
fn();
} catch(e) {
return true;
}

return false;
}

// matches the format of BitArray.toSting()
function toString( arr ) {
Expand Down Expand Up @@ -55,9 +67,32 @@ const binary_operations = (()=>{

})();

/** suite 4 */
const character_encoding_from_set = {
".encodeWithCharacterSet_1bit": sample3.encodeWithCharacterSet('ab') === 'abba',
".encodeWithCharacterSet_3bit": sample3.encodeWithCharacterSet('abcdefgh') === 'da',
".encodeWithCharacterSet_": expectThrow(() => sample3.encodeWithCharacterSet('')),
".encodeWithCharacterSet_a": expectThrow(() => sample3.encodeWithCharacterSet('a')),
".encodeWithCharacterSet_abc": expectThrow(() => sample3.encodeWithCharacterSet('abc'))
};

/** suite 5 */
const character_encode_decode = {
".decodeWithCharacterSet_1bit": BitArray.decodeWithCharacterSet('ab', 'abba').toString() === sample3.toString(),
// Note: the substring is needed because when deserializing, we have some number of padding 0s that we can't know were
// in the original string or not
".decodeWithCharacterSet_3bit": BitArray.decodeWithCharacterSet('abcdefgh', 'da').toString().substring(0, 4) === sample3.toString(),
".decodeWithCharacterSet_empty": BitArray.decodeWithCharacterSet('ab', '').toString() === '',
".decodeWithCharacterSet_invalid": expectThrow(() => BitArray.decodeWithCharacterSet('ab', 'abc')),
".decodeWithCharacterSet_": expectThrow(() => BitArray.decodeWithCharacterSet('', 'abba'))
};


export default {
instantiating,
properties,
binary_operations
binary_operations,
character_encoding_from_set,
character_encode_decode
};