Here is a more declarative implementation which Jsperf prefers by a decent margin.
let replaceRowCol = (matrix, trg=0, val=0) => {
// Store width and height of matrix
let w = matrix.length;
let h = matrix[0].length;
// An array to hold all replacements that need to be made
let replaceArr = [];
// Any coord whose value is `trg` results in a replacement at that coord
for (let x = 0; x < w; x++) { for (let y = 0; y < h; y++) {
if (matrix[x][y] === trg) replaceArr.push({ x, y });
}}
// Perform all replacements
for (let { x, y } of replaceArr) {
for (let xx = 0; xx < w; xx++) matrix[xx][y] = val;
for (let yy = 0; yy < h; yy++) matrix[x][yy] = val;
}
return matrix;
};
let matrix = replaceRowCol([
[ 0, 2, 3, 4 ],
[ 1, 2, 3, 4 ],
[ 1, 2, 0, 4 ],
[ 1, 2, 3, 4 ]
]);
for (let row of matrix) console.log(row.join(' '));
Instead of inserting temporary placeholder values into the matrix, this approach performs a first-pass to remember the locations of all zeroes, and a second pass over these memorized locations to replace the rows and cols.
The speed is a result of avoiding callbacks and function calls, with the exception of Array.prototype.push
(and note this could be avoided if we used a simple linked list instead). Even though callbacks get compiled and are very quick under any decent javascript VM, they still aren't as fast as good old procedural code, like the above.
It's worth mentioning the possibility for a very cheeky speedup: if multiple rows need to be replaced, you could set every item in the first such row to zeroes, but for every row after, you could reference the first zeroed row. This would entirely skip the need to iterate later rows. Of course, the resulting matrix would have multiple rows that are all references to the same array, which could lead to highly unexpected results during future operations.
This cheeky speedup would look something like this:
let replacedX = null;
for (let { x, y } of replaceArr) {
// We still need to replace the column, as usual
for (let xx = 0; xx < w; xx++) matrix[xx][y] = val;
if (replacedX === null) {
replacedX = matrix[x] = Array(w).fill(val); // This has O(w) runtime
} else {
matrix[x] = replacedX; // This has O(1) runtime
}
}
EDIT: Linked list implementation. It's included in the jsperf, and it's a bit faster!
let replaceRowColLinkedList = (matrix, trg=0, val=0) => {
// Store width and height of matrix
let w = matrix.length;
let h = matrix[0].length;
// A linked list
let replacements = null;
// Any coord whose value is `trg` results in a replacement at that coord
for (let x = 0; x < w; x++) { for (let y = 0; y < h; y++) {
if (matrix[x][y] === trg) {
let next = replacements;
replacements = { x, y, next };
}
}}
// Perform all replacements
let llNode = replacements;
while (llNode) {
let { x, y, next } = llNode;
for (let xx = 0; xx < w; xx++) matrix[xx][y] = val;
for (let yy = 0; yy < h; yy++) matrix[x][yy] = val;
llNode = next;
}
return matrix;
};
let matrix = replaceRowColLinkedList([
[ 0, 2, 3, 4 ],
[ 1, 2, 3, 4 ],
[ 1, 2, 0, 4 ],
[ 1, 2, 3, 4 ]
]);
for (let row of matrix) console.log(row.join(' '));
EDIT: For large matrices with many collisions (many zeroes which share the same row or column) it may be worth using Set
to avoid re-zeroing rows/cols. The downside, of course, is that Set.prototype.add
must have a runtime worse than O(1)
, regardless of implementation. Overall, however, the runtime of Set.prototype.add
is negligible for smaller matrices, and for larger matrices there can be significant benefits from avoiding collisions. This method is consistently fastest in the jsperf, and the method I overall recommend!
let replaceRowCol = (matrix, trg=0, val=0) => {
// Store width and height of matrix
let w = matrix.length;
let h = matrix[0].length;
// Store columns and rows to replace
let replaceX = new Set();
let replaceY = new Set();
// Any coord whose value is `trg` results in a replacement at that coord
for (let x = 0; x < w; x++) { for (let y = 0; y < h; y++) {
if (matrix[x][y] === trg) {
replaceX.add(x);
replaceY.add(y);
}
}}
// Perform all replacements
for (let x of replaceX) for (let y = 0; y < h; y++) matrix[x][y] = val;
for (let y of replaceY) for (let x = 0; x < w; x++) matrix[x][y] = val;
return matrix;
};
let matrix = replaceRowCol([
[ 0, 2, 3, 4, 5, 6 ],
[ 1, 2, 3, 4, 5, 6 ],
[ 1, 2, 3, 4, 5, 6 ],
[ 1, 2, 3, 0, 5, 6 ],
[ 1, 2, 3, 4, 5, 6 ]
]);
for (let row of matrix) console.log(row.join(' '));