Skip to content

Instantly share code, notes, and snippets.

@liath
Last active May 21, 2024 19:37
Show Gist options
  • Save liath/f4d31e58d2fb8b2fa9a5fe7474ac561b to your computer and use it in GitHub Desktop.
Save liath/f4d31e58d2fb8b2fa9a5fe7474ac561b to your computer and use it in GitHub Desktop.
Base64 encoder golfing in JS
(i,f=([a,b,c,...z])=>1+a?[a>>2,(a<<4)+(b>>4),b+1&&(b<<2)+(c>>6),c,...f(z)]:z)=>String.fromCharCode(...f(i).map(x=>(x%=64)+1?x+71-(x<26?6:x<52?0:x<62?75:x&1?87:90):61))
(i, // input bytes iterable
f= // abuse the default value syntax to name our recursion fn
([a,b,c,...z])=> // eat three elements at a time and save any remaining to a new list `z`
1+a? // check if there were any bytes left to consume, 1+a prevents a=0 from being interpreted falsey as 0+1==true but undefined+1==false
[a>>2, // Take the upper six bits of the first byte by shifting two bits into the void: xxxxxx|xx
(a<<4)+(b>>4), // take the lower half of the first byte and upper half of the second
b+1&& // bitwise operators convert NaN to 0 and we want NaNs
(b<<2)+(c>>6), // lower 2 bits of the second byte with upper 4 of the third
c, // and take all of the last byte, we only want six bits for each of these so we'll lop of the upper two bits later
...f(z)]:z // continue with rest of z, or if a was undefined above, z will be an empty array so we return that to appease the spread operator
)=>
String.fromCharCode( // convert numbers to char string
...f(i) // call f with the input and curry the elements into fromCharCode to get a string without having to join them
.map(x=> // foreach number returned by f(i)
(x%=64)+1? // %64 leaves us with only the lower six bits and +1? checks for NaNs
x+71- // start at an offset into the ascii table
(x<26?6: // A-Z starts at 65 in ascii, so -6
x<52?0: // a-z starts at 97 and x>26, x+71=97, so -0
x<62?75: // 0-9 starts at 48, x>52, x+71-75=48, so -75
x&1? // if we got this far it means the number is either 62 or 63, 62&1=0 and 63&1=1
87: // 63&1=1, 63+71-87=47 => "/"
90) // 62&1=0, 62+71-90=43 => "+"
:61 // the NaN check above directed us here so output padding char, 61 => "="
))
@liath
Copy link
Author

liath commented May 14, 2024

Using array destructuring with recursion I got down to 172 with a static alphabet:

(i,f=([a,b,c,...z])=>[a>>2,(a<<4)+(b>>4),(b<<2)+(c>>6),c,...z[0]?f(z):z])=>f(i).map(x=>'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[x%64]||'=').join``

But then I found the very nice shortcut for the alphabet lookup given here:
https://codegolf.stackexchange.com/a/125830

So now we're at 166!

@liath
Copy link
Author

liath commented May 14, 2024

Had to beef up the NaN check as it was making 0x00 bytes in the input into padding chars lol
At 169 now!

@liath
Copy link
Author

liath commented May 14, 2024

167 with fixed checks on the recursion and remembering NaN+1===NaN and NaN is falsey

@liath
Copy link
Author

liath commented May 14, 2024

166 again!
f^63 to check for 62 vs 63 can be shortened to f&1 as we already know the value must be one of those two values

@liath
Copy link
Author

liath commented May 18, 2024

162! Used spread operator to pass chars in one go to String.fromCharCode which saved a call to .join

@liath
Copy link
Author

liath commented May 21, 2024

167 :<
Had to add a NaN guard to prevent erroneous trailing nulls

> Buffer.from([0x8c, 0x82, 0x7f, 0x20]).toString('base64')
'jIJ/IA=='

> ((i,f=([a,b,c,...z])=>1+a?[a>>2,(a<<4)+(b>>4),b+1&&(b<<2)+(c>>6),c,...f(z)]:z)=>String.fromCharCode(...f(i).map(x=>(x%=64)+1?x+71-(x<26?6:x<52?0:x<62?75:x&1?87:90):61)))(Buffer.from([0x8c, 0x82, 0x7f, 0x20]))
'jIJ/IA=='

> ((i,f=([a,b,c,...z])=>1+a?[a>>2,(a<<4)+(b>>4),(b<<2)+(c>>6),c,...f(z)]:z)=>String.fromCharCode(...f(i).map(x=>(x%=64)+1?x+71-(x<26?6:x<52?0:x<62?75:x&1?87:90):61)))(Buffer.from([0x8c, 0x82, 0x7f, 0x20]))
'jIJ/IAA='

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment