JavaScript - 673 707 730 751
e=[],g=[],h=[],m=[],q=[];function r(){a=s,b=t;function d(d,A){n=a+d,p=b+A;c>e[n][p]&&(u=!1,v>e[n][p]&&(v=e[n][p],w=n,k=p))}c=e[a][b],u=!0,v=c,w=a,k=b;0!=a&&d(-1,0);a!=l&&d(1,0);0!=b&&d(0,-1);b!=l&&d(0,1);g[a][b]=w;h[a][b]=k;return u}function x(a,b,d){function c(a,b,c,k){g[a+b][c+k]==a&&h[a+b][c+k]==c&&(d=x(a+b,c+k,d))}d++;0!=a&&c(a,-1,b,0);a!=l&&c(a,1,b,0);0!=b&&c(a,0,b,-1);b!=l&&c(a,0,b,1);return d}y=$EXEC('cat "'+$ARG[0]+'"').split("\n");l=y[0]-1;for(z=-1;z++<l;)e[z]=y[z+1].split(" "),g[z]=[],h[z]=[];for(s=-1;s++<l;)for(t=-1;t++<l;)r()&&m.push([s,t]);for(z=m.length-1;0<=z;--z)s=m[z][0],t=m[z][1],q.push(x(s,t,0));print(q.sort(function(a,b){return b-a}).join(" "));
Test results (using Nashorn):
$ for i in A B C D; do jjs -scripting minlm.js -- "test$i"; done 7 2 1 11 7 7 7 5 4 $
There would probably be stack problems for maps of size 5000 (but that's an implementation detail :).
The unminified source in all it's fugliness:
// lm.js - find the local minima // Globalization of variables. /* The map is a 2 dimensional array. Indices for the elements map as: [0,0] ... [0,n] ... [n,0] ... [n,n] Each element of the array is a structure. The structure for each element is: Item Purpose Range Comment ---- ------- ----- ------- h Height of cell integers s Is it a sink? boolean x X of downhill cell (0..maxIndex) if s is true, x&y point to self y Y of downhill cell (0..maxIndex) Debugging only: b Basin name ('A'..'A'+# of basins) Use a separate array-of-arrays for each structure item. The index range is 0..maxIndex. */ var height = []; var sink = []; var downhillX = []; var downhillY = []; //var basin = []; var maxIndex; // A list of sinks in the map. Each element is an array of [ x, y ], where // both x & y are in the range 0..maxIndex. var basinList = []; // An unordered list of basin sizes. var basinSize = []; // Functions. function isSink(x,y) { var myHeight = height[x][y]; var imaSink = true; var bestDownhillHeight = myHeight; var bestDownhillX = x; var bestDownhillY = y; /* Visit the neighbors. If this cell is the lowest, then it's the sink. If not, find the steepest downhill direction. This would be the place to test the assumption that "If a cell is not a sink, you may assume it has a unique lowest neighbor and that this neighbor will be lower than the cell." But right now, we'll take that on faith. */ function visit(deltaX,deltaY) { var neighborX = x+deltaX; var neighborY = y+deltaY; if (myHeight > height[neighborX][neighborY]) { imaSink = false; if (bestDownhillHeight > height[neighborX][neighborY]) { bestDownhillHeight = height[neighborX][neighborY]; bestDownhillX = neighborX; bestDownhillY = neighborY; } } } if (x !== 0) { // upwards neighbor exists visit(-1,0); } if (x !== maxIndex) { // downwards neighbor exists visit(1,0); } if (y !== 0) { // left-hand neighbor exists visit(0,-1); } if (y !== maxIndex) { // right-hand neighbor exists visit(0,1); } downhillX[x][y] = bestDownhillX; downhillY[x][y] = bestDownhillY; return imaSink; } function exploreBasin(x,y,currentSize) {//,basinName) { // This cell is in the basin. //basin[x][y] = basinName; currentSize++; /* Visit all neighbors that have this cell as the best downhill path and add them to the basin. */ function visit(x,deltaX,y,deltaY) { if ((downhillX[x+deltaX][y+deltaY] === x) && (downhillY[x+deltaX][y+deltaY] === y)) { currentSize = exploreBasin(x+deltaX,y+deltaY,currentSize); //,basinName); } return 0; } if (x !== 0) { // upwards neighbor exists visit(x,-1,y,0); } if (x !== maxIndex) { // downwards neighbor exists visit(x,1,y,0); } if (y !== 0) { // left-hand neighbor exists visit(x,0,y,-1); } if (y !== maxIndex) { // right-hand neighbor exists visit(x,0,y,1); } return currentSize; } // Read map from file (1st argument). var lines = $EXEC('cat "' + $ARG[0] + '"').split('\n'); maxIndex = lines.shift() - 1; for (var i = 0; i<=maxIndex; i++) { height[i] = lines.shift().split(' '); // Create all other 2D arrays. sink[i] = []; downhillX[i] = []; downhillY[i] = []; //basin[i] = []; } // Everyone decides if they are a sink. Create list of sinks (i.e. roots). for (var x=0; x<=maxIndex; x++) { for (var y=0; y<=maxIndex; y++) { if (sink[x][y] = isSink(x,y)) { // This node is a root (AKA sink). basinList.push([x,y]); } } } //for (var i = 0; i<=maxIndex; i++) { print(sink[i]); } // Each root explores it's basin. //var basinName = 'A'; for (var i=basinList.length-1; i>=0; --i) { // i-- makes Closure Compiler sad var x = basinList[i][0]; var y = basinList[i][1]; basinSize.push(exploreBasin(x,y,0)); //,basinName)); //basinName = String.fromCharCode(basinName.charCodeAt() + 1); } //for (var i = 0; i<=maxIndex; i++) { print(basin[i]); } // Done. print(basinSize.sort(function(a, b){return b-a}).join(' '));
I got better minimization results by breaking up the element objects into separate arrays, globalizing everywhere possible and embracing side-effects. NSFW.
The effects of code minimization:
- 4537 bytes, unminified
- 1180 bytes, packer
- 855 bytes, packer + hand optimizations (1 character global names)
- 751 bytes, Google Closure Compiler with ADVANCED_OPTIMIZATIONS (NB, it elided a vestigial "return 0" as dead code)
- 730 bytes, reckless hand optimization (I'm not changing the unminified source, so NSFW)
- 707 bytes, more reckless hand optimization (remove all references to sink[]);
- 673 bytes, remove all "var"s, drop Nashorn -strict flag
I could have achieved close to 700 bytes without editing the minimized code if I'd been willing to modify the original source. But I didn't because I think leaving it as-is gives an interesting view from the starting point.