Last active
September 30, 2015 02:17
-
-
Save zmaril/1705450 to your computer and use it in GitHub Desktop.
Turning any number into +[]'s with javascript
This file contains 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"; | |
// This is one of my favorite javascript tricks. It shows off some of the weird | |
// parts of the language in a pretty straightforward way. With only three | |
// characaters, '+', '[' and ']', we can encode any natural number we want in javascript. For | |
// the math minded, this was inspiried by Peano's Axioms and javascript's type conversions. | |
// http://mathworld.wolfram.com/PeanosAxioms.html | |
// This file uses es6, run it by doing `node --harmony peano.js` | |
// The unary plus (calling plus with one argument) converts the single argument | |
// to an equivalent number. Per the javascript standard, an empty array converts | |
// to 0 and an array with a single element recursively calls the type conversion | |
// on the element inside. An array with multiple elements returns a NaN. Note | |
// that we are using strict equality here, `===`, and that this doesn't rely on | |
// weird string type conversions using `==`. | |
for (let i = 0; i < 10; i++){ | |
console.assert(i === +[i]); | |
console.assert(isNaN(+[i,i])); | |
} | |
// Next, we use the ++value operator, which just increments the passed in value. | |
// We need to use the `++` operator on the left side of the value, because `++` | |
// on the left increments the value before it is used in the expression while | |
// `++` on the right increments the value after it has been used in the | |
// expression. | |
for (let i = 0; i < 10; i++){ | |
console.assert(i+1 === ++i); | |
console.assert(i+1 !== i++); | |
} | |
// Now, we see that we have a way of representing zero with our restricted | |
// character set, as well as incrementing any value. However, it is a bit tricky | |
// to combine them because of how javascript is parsed. | |
try { eval("+++[]"); console.log("Sadly, \"++[]\" is a parse error and this isn't printed.");} catch (err){}; | |
// So, we can use a little slight of hand here. By putting a value into an array | |
// and pulling it back out afterwards, the value stays the same, but we start | |
// with a fresh slate in terms of syntax. Thankfully, javascript uses zero based | |
// indexing, so we can use our previous representation of zero to pull the value | |
// back out of the array via the `value[i]` syntax. Furthermore, and this is | |
// pure luck, array access via bracket notation takes higher precendence than | |
// the increment operation. | |
for (let i = 0; i < 10; i++){ | |
console.assert(i === [i][0]); | |
console.assert(i === [i][+[]]); | |
console.assert(i+1 === ++[i][+[]]); | |
} | |
//So, by wrapping a value up like "++[value][+[]]" we can increment the value by | |
//one. Thankfully this composes, but now we are to the point where we are | |
//creating strings of code within our code that need to be evaluated. So, we'll | |
//just use `eval` on the results of the increments and it works! | |
function increment(s) { | |
return "++["+ s +"][+[]]"; | |
}; | |
for (let i = 0; i < 10; i++){ | |
console.assert(i+1 === eval(increment(i))); | |
console.assert(i+2 === eval(increment(increment(i)))); | |
console.assert(i+3 === eval(increment(increment(increment(i))))); | |
} | |
// So, finally, we are at the point where we can take in any natural number and | |
// return a representation of it encoded in only '+','[' and ']'. | |
function encode(n){ | |
let s = "+[]"; | |
for (let i = 0 ; i < n ; i++){ | |
s = increment(s); | |
} | |
return s; | |
}; | |
for (let i = 0; i < 10; i++){ | |
console.assert(i === eval(encode(i))); | |
} | |
// And that's how you can create javascript code strictly recreates naturally numbers. | |
for (let i = 0; i < 10; i++){ | |
console.log("Encoding "+ i); | |
console.log(encode(i)); | |
console.log("\n"); | |
} | |
// If you relax the constraint that you strictly encode a natural number and | |
// rely on loose type equality, you can compress the representation down by | |
// encoding each digit and creating a string instead. There are a few more type | |
// tricks in the compress function, but they aren't so complicated to figure out | |
// compared to unary plus. By trading strict equalitly for loose, we get a | |
// logarithmic decrease in representation size. | |
function compress (n){ | |
return String(n).split("").map(encode).map(v => "[" + v + "]").join("+"); | |
}; | |
for (let i = 0; i < 10; i++ ){ | |
console.assert(i === eval(encode(i))); | |
} | |
for (let i = 0; i < 100; i += 11){ | |
console.log("Compressing "+ i); | |
console.log(compress(i)); | |
console.log("\n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment