Sudoku solver in CoffeeScript 9
CoffeeScript is inspired by Ruby and Python but what's most peculiar with it is that it compiles to JavaScript. The generated JavaScript isn't all that bad and it even passes JavaScript lint without warnings.
"Underneath all of those embarrassing braces and semicolons, JavaScript has always had a gorgeous object model at its heart." - Jeremy Ashkenas
About a week ago I stumbled on the very clever Sudoku solver by Peter Norvig. I have nothing (or at least not much) against Python but I pretty soon checked out the Ruby translations of the solver. I then refactored one of the solutions to get a chance to get to know the algorithm better.
Now that I finally installed CoffeeScript the Sudoku solver came to mind. I dived in head first and got in trouble pretty soon. It turns out that Array Comprehensions in CoffeeScript differs some from the List Comprehensions in Python.
1 2 3 |
def cross(A, B): "Cross product of elements in A and elements in B." return [a+b for a in A for b in B] |
returns an one-dimensional array if you call it with two arrays (or strings).
But in CoffeeScript
cross = (A, B) -> (a+b for a in A for b in B) |
returns a two-dimensional array.
The jury is still out on if this is intended or not but either way the array comprehensions in CoffeeScript are still very useful.
It's been decided that this is by design. The issue has been closed.
For the cross-function I ended up with
1 2 3 4 5 6 |
cross = (A, B) -> results = [] for a in A for b in B results.push a + b results |
Using `map` I could have got much closer to the Ruby version.
1 2 |
cross = (cols, rows) -> [].concat (cols.map (x) -> rows.map (y) -> y+x)... |
You can find the CoffeScript Sudoku solver as a Gist. Compile it with
coffee -c sudoku.coffee
and then run it with
node sudoku.js
I don't know how I missed it but as Trevor pointed out below all you have to do is
coffee sudoku.coffee
1 2 3 4 5 6 7 8 9
|------+-----+-----|
A | 4 8 3|9 2 1|6 5 7|
B | 9 6 7|3 4 5|8 2 1|
C | 2 5 1|8 7 6|4 9 3|
|------+-----+-----|
D | 5 4 8|1 3 2|9 7 6|
E | 7 2 9|5 6 4|1 3 8|
F | 1 3 6|7 9 8|2 4 5|
|------+-----+-----|
G | 3 7 2|6 8 9|5 1 4|
H | 8 1 4|2 5 3|7 6 9|
I | 6 9 5|4 1 7|3 8 2|
|------+-----+-----|
Here's the generated sudoku.js.
This is the only CoffeeScript I've ever written but I already like it (more than JavaScript). Please correct me if I strayed from the CoffeeScript way.
Encrypt images in JavaScript
In browsers that supports the HTML5 canvas it's possible to read the raw image data. I figured that this makes it possible to encrypt images on the client. Why would one ever want to encrypt images client-side? One use case could be to send images to a server that only those who know the password can decrypt (host-proof hosting).
I used Crypto-JS to encrypt with AES and Rabbit.
First I get the CanvasPixelArray from the ImageData object.
1 2 3 4 |
var ctx = document.getElementById('leif') .getContext('2d'); var imgd = ctx.getImageData(0,0,width,height); var pixelArray = imgd.data; |
The pixel array has four bytes for each pixel as RGBA but Crypto-JS encrypts a string, not an array. At first I used
.join() and .split(",") to get from array to string and back. It was slow and the string got much longer than it had to be. Actually four times longer. To save even more space I decided to discard the alpha channel.
1 2 3 4 5 6 7 8 9 10 |
function canvasArrToString(a) { var s=""; // Removes alpha to save space. for (var i=0; i<pix.length; i+=4) { s+=(String.fromCharCode(pix[i]) + String.fromCharCode(pix[i+1]) + String.fromCharCode(pix[i+2])); } return s; } |
That string is what I then encrypt. I sticked to `+=` after reading String Performance an Analysis.
var encrypted = Crypto.Rabbit.encrypt(imageString, password); |
I used a small 160x120 pixels image. With four bytes for each pixels that gives 76800 bytes. Even though I stripped the alpha channel the encrypted image still takes up 124680 bytes, 1.62 times bigger. Using `.join()` it was 384736 bytes, 5 times bigger. One cause for it still being larger than the original image is that Crypto-JS returns a Base64 encoded string and that adds something like 37%.
Before I could write it back to the canvas I had to convert it to an array again.
1 2 3 4 5 6 7 8 9 10 |
function canvasStringToArr(s) { var arr=[]; for (var i=0; i<s.length; i+=3) { for (var j=0; j<3; j++) { arr.push(s.substring(i+j,i+j+1).charCodeAt()); } arr.push(255); // Hardcodes alpha to 255. } return arr; } |
Decryption is simple.
1 2 3 4 |
var arr=canvasStringToArr( Crypto.Rabbit.decrypt(encryptedString, password)); imgd.data=arr; ctx.putImageData(imgd,0,0); |
Tested in Firefox, Google Chrome, WebKit3.1 (Android 2.2), iOS 4.1, and a very recent release of Opera.
| Browser / Action in milliseconds | Enc. Rabbit | Dec. Rabbit | Enc. AES | Dec. AES |
| Google Chrome 6.0.472.62 C2D@1.86GHz | 136 | 130 | 236 | 222 |
| Opera 10.63 P4HT@3GHz | 246 | 252 | 438 | 437 |
| Google Chrome 6.0.472.63 P4HT@3GHz | 280 | 648 | 303 | 547 |
| Firefox 3.6.10 Phenom II X4 945@3GHz | 494 | 321 | 1876 | 1745 |
| Firefox 3.6.10 i5@3,5GHz | 366 | 193 | 1639 | 1410 |
| Firefox 3.6.10 C2D@1.86GHz | 760 | 367 | 2417 | 1983 |
| Firefox 3.6.10 P4HT@3GHz | 880 | 440 | 4000 | 3500 |
| Nokia N900 Chrome | 1764 | 1975 | 2509 | 2508 |
| WebKit 3.1 (HTC Desire) | 2000 | 2200 | 3300 | 3400 |
| iPhone 3GS iOS 4.1 | 2130 | 2071 | 7198 | 7131 |
| N900 Firefox Mobile | 3411 | 3508 | 19308 | 19466 |
| N900 native (MicroB) | 4681 | 4300 | 24560 | 20973 |
| X10 Mini Pro, Android 1.6 | 7464 | 7747 | timeout | timeout |
A demo can be found here.
EDIT 2010-10-17
Warning I've noticed that the encrypted strings aren't compatible between browsers. At least not in between Google Chrome and Firefox. I don't know why.
I also tried to add deflate/inflate and that compresses my test image to a third of the raw size and in Google Chrome it also halved the execution time. In Firefox Rabbit got about 50% slower and AES about 50% faster with deflate/inflate.
Here's a demo of this.
EDIT 2010-10-21
Added a preprocessing filter of the raw image data inspired by PNG type 1 sub filter. Presented my idea to Fredrik and he returned a formula. Thanks!
The result wasn't what I had hoped for, a measly 6% smaller. I also tried to save the image as real PNG and that one is 20480 bytes.
20480*1.37=28057 bytes (Base64 overhead) and my homemade format is 38336 bytes. Not a fantastic result but not that horrible either.
Here's a demo of this.
JavaScript hash table keys 2
In JavaScript you can add properties to objects dynamically. You can access those properties both by object.foo and object['foo']. The later is commonly used to use JavaScript objects as hash tables (associative arrays).
While implementing a simplistic unique random number generator I happened to use keys(obj). Unfortunately keys(obj) is part of ECMAScript 5. See chapter 15.2.3.14 in ECMA-262. The web browsers of today mostly implements ECMAScript 3.
Here's an implementation of keys(obj) for ECMAScript 3 browsers (tested in Google Chrome, IE8 and Firefox 3.5). If the browser already has a keys function then nothing will be done.
1 2 3 4 5 6 7 8 9 |
if (typeof keys == "undefined") { var keys = function(ob) { props=[]; for (k in ob) if (ob.hasOwnProperty(k)) props.push(k); return props; } } |
The simplistic unique random number generator looks like this
1 2 3 4 5 6 |
function uniqueRndNumbers(min, max, quantity) { var ht={}, i=quantity; while ( i>0 || keys(ht).length<quantity) ht[Math.floor(Math.random()*(max-min+1))+min]=i--; return keys(ht); } |
This function has not undergone any serious testing. Also if the quantity is more than a fraction of (max-min) then another algorithm like the Fisher–Yates shuffle might be a better choice.
