简体   繁体   中英

Optimizing shape detection algorithm

I have matrix of 0s and 1s and i need to extract "shapes" from that matrix. Shapes consists of 1s connected in 8 directions. In the final result i get 2d array of shapes. Every array of shape consits of cell indexes in original matrix. I wrote script and it works, except it crashes with larger matrixes. And i get error in console Uncaught RangeError: Maximum call stack size exceeded I need to make it work with large matrixes like 1000 x 1000 and more.

Here's my code:

 var matrixCols = 150; var matrixRows = 150; var matrix = []; var shapes = []; var checkedCels = []; function createMatrix() { for(var i = 0; i < matrixRows; i++) { var row = []; for(var j = 0; j < matrixRows; j++) { var value = Math.round(Math.random()); row.push(value); matrix.push(value); } console.log(JSON.stringify(row)); } } function getShapes() { for(var i = 0; i < matrix.length; i++) { if(checkedCels.indexOf(i) === -1 && matrix[i] === 1) { shapes.push(formShape(i)); } } console.log('Total shapes:', shapes.length); console.log(shapes); } function formShape(startIndex) { return getNeighbours(startIndex); } function getNeighbours(index) { if(checkedCels.indexOf(index) > -1) { return []; } var cels = [index]; checkedCels.push(index); var nwIndex = index - matrixCols - 1; var nwCel = matrix[nwIndex]; if(typeof nwCel !== 'undefined' && nwCel === 1 && index % matrixCols > 0) { cels = cels.concat(getNeighbours(nwIndex)); } var nIndex = index - matrixCols; var nCel = matrix[nIndex]; if(typeof nCel !== 'undefined' && nCel === 1) { cels = cels.concat(getNeighbours(nIndex)); } var neIndex = index - matrixCols + 1; var neCel = matrix[neIndex]; if(typeof neCel !== 'undefined' && neCel === 1 && index % matrixCols < (matrixCols - 1)) { cels = cels.concat(getNeighbours(neIndex)); } var wIndex = index - 1; var wCel = matrix[wIndex]; if(typeof wCel !== 'undefined' && wCel === 1 && index % matrixCols > 0) { cels = cels.concat(getNeighbours(wIndex)); } var eIndex = index + 1; var eCel = matrix[eIndex]; if(typeof eCel !== 'undefined' && eCel === 1 && index % matrixCols < (matrixCols - 1)) { cels = cels.concat(getNeighbours(eIndex)); } var swIndex = index + matrixCols - 1; var swCel = matrix[swIndex]; if(typeof swCel !== 'undefined' && swCel === 1 && index % matrixCols > 0) { cels = cels.concat(getNeighbours(swIndex)); } var sIndex = index + matrixCols; var sCel = matrix[sIndex]; if(typeof sCel !== 'undefined' && sCel === 1) { cels = cels.concat(getNeighbours(sIndex)); } var seIndex = index + matrixCols + 1; var seCel = matrix[seIndex]; if(typeof seCel !== 'undefined' && seCel === 1 && index % matrixCols < (matrixCols - 1)) { cels = cels.concat(getNeighbours(seIndex)); } return cels; } createMatrix(); getShapes(); 

How I can optimize it?

You can avoid the recursion in the getNeighbours function, by maintaining a list of cells to check (which initially contains just the startIndex ) and, at each iteration:

  • take an item from the list
  • check if it is OK (not checked, and the matrix value at that index is 1)
  • add its neighbours to the list to check
  • repeat

This cycle stops when there are no more cells to check.

By not using recursion, the program will not run out of stack space, and the algorithm will be able to handle bigger matrices (as long as the current list to check does not exceed the available memory).

In addition to that, there's another possible optimization: checkedCells is currently an array, and to see if a cell has already been analyzed you use .indexOf() (which is an O(n) operation). Changing checkedCells to be an object that keeps as keys the indices of analyzed cells will reduce this lookup to O(1).

Here's your code modified according to the two points above:

 console.clear(); var matrixCols = 7; var matrixRows = 7; var matrix = []; var shapes = []; var checkedCels = {}; function createMatrix() { for (var i = 0; i < matrixRows; i++) { var row = []; for (var j = 0; j < matrixRows; j++) { var value = Math.round(Math.random()); // value = Math.random() > 0.75 ? 1 : 0; // to change the probability of a cell being "set" row.push(value); matrix.push(value); } console.log(row.map(function(x) { return " X" [x] }).join('')); } } function getShapes() { for (var i = 0; i < matrix.length; i++) { if (!checkedCels[i] && matrix[i] === 1) { shapes.push(formShape(i)); } } console.log('Total shapes:', shapes.length); console.log(shapes); } function formShape(startIndex) { var cels = []; var toCheck = [startIndex]; while (toCheck.length) { var index = toCheck.pop(); if (checkedCels[index]) { continue; } if (matrix[index] !== 1) { continue; } cels.push(index); checkedCels[index] = 1; var neighbours = []; if (index % matrixCols > 0) { neighbours.push(index - matrixCols - 1); // NW neighbours.push(index - 1); // W neighbours.push(index + matrixCols - 1); // SW } if (index % matrixCols < (matrixCols - 1)) { neighbours.push(index - matrixCols + 1); // NE neighbours.push(index + 1); // E neighbours.push(index + matrixCols + 1); // SE } neighbours.push(index - matrixCols); // N neighbours.push(index + matrixCols); // S neighbours.forEach(function(n) { if (typeof matrix[n] !== 'undefined') { toCheck.push(n); } }); } return cels; } createMatrix(); getShapes(); 

I have limited the matrix size to 7x7 for readability, but the code above should solve 1000x1000 matrices in a few seconds.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM