These functions will compress 200 booleans to a 40-character, URL-safe string, and expand them back to the original array of booleans. They should work for any length boolean array, growing by approximately one character for every six booleans:
const compressBools = (bools) =>
String (bools .length) + '~' +
btoa ( bools
.map (b => b ? '1' : '0')
.reduce (
([c, ...r], b, i) => (bools .length - i) % 8 == 0 ? [[b], c, ...r] : [[...c, b], ...r],
[[]]
)
.reverse ()
.map (a => a .join (''))
.map (s => parseInt(s, 2))
.map (n => String.fromCharCode(n))
.join ('')
)
.replace (/\+/g, '-')
.replace (/\//g, '_')
.replace (/\=/g, '.')
const expandBools = (s, [len, str] = s .split ('~')) =>
atob (str
.replace (/\./g, '=')
.replace (/_/g, '/')
.replace (/\-/g, '+')
)
.split ('')
.map (c => c .charCodeAt (0))
.map (s => Number (s) .toString (2) .padStart (8, '0'))
.flatMap (a => a .split (''))
.slice (-len)
.map (c => c == '1')
const arr = Array.from({length: 200}, _ => Math.random() < .5)
const compressed = compressBools (arr)
console .log (`Compressed String: "${compressed}"`)
const expanded = expandBools(compressed)
console .log (`Output matches: ${expanded.every((b, i) => b == arr[i])}`)
The three regex replacements in each are to deal with the +
and /
characters of the underlying base64 conversion, as well as its =
padding character, replacing them with URL-safe alternatives. You could instead call encode/decodeURIComponent
, but this way leads to shorter strings.
The ugly reduce
in the compression is to split a long string of 0's and 1's into groups of 8, with the first one potentially shorter. This gives us bytes which we can then convert into characters.
Note that the output string starts with the count of booleans to generate. This is because we could not otherwise distinguish some leading zeros in the numbers -- which would get translated into initial false
s -- from arrays which were simply shorter and didn't have such leading zeros. That number is separated from the remaining string by a tilde (~
); you could easily replace this with another character if you like, but URL-safe special characters are hard to come by.
There is also a little game we could play if we liked with these, finding boolean arrays that lead to interesting strings. For instance:
const arr = [true, false, false, true, false, true, false, true, true, false, true, false, true, true, false, true, false, false, true, true, true, false, false, true, false, false, true, false, false, true, true, true, true, true, true, false, false, true, true, true, false, true, false, true, true, true, true, false, true, true, true, true, false, true, false, true, false, true, true, false, true, true, true, true, true, true, false, false, true, false, true, true, false, true, false, false, false, true, true, false, false, false, false, true, true, true, true, true, true, false, true, false, false, false, false, true, false, true, true, true, false, false, true, true, true, true, false, true, false, true, true, false, false, true, false, true, true, false, true, true, false, false, false, true, false, true, false, true, false, false, false, true, false, false, true, true, true, true, true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, false, true, true, true, true, true, false, true, true, true, false, true, true, false, true, true, false, true, true, true, true, true, true, false, false, true, true, true, false, true, true, true, true, true, true, false, true, true]
console .log (compressBools (arr)) //~> "191~Stack_Overflow_Question_59923537"