This is a challenge from CodeSignal.
For
image = [[1, 1, 1],
[1, 7, 1],
[1, 1, 1]]
the output should be boxBlur(image) = [[1]]
.
To get the value of the middle pixel in the input 3 ×ばつ 3 square: (1 + 1 + 1 + 1 + 7 + 1 + 1 + 1 + 1) = 15 / 9 = 1.66666 = 1
. The border pixels are cropped from the final result.
For
image = [[7, 4, 0, 1],
[5, 6, 2, 2],
[6, 10, 7, 8],
[1, 4, 2, 0]]
the output should be
boxBlur(image) = [[5, 4],
[4, 4]]
There are four 3 ×ばつ 3 squares in the input image, so there should be four integers in the blurred output. To get the first value: (7 + 4 + 0 + 5 + 6 + 2 + 6 + 10 + 7) = 47 / 9 = 5.2222 = 5
. The other three integers are obtained the same way, then the surrounding integers are cropped from the final result.
Here's my code:
function boxBlur(image) {
const SQUARE = 3
const outerArr = []
for (let i = 0; i < image.length; i++) {
const innerArr = []
for (let j = 0; j < image[0].length; j++) {
if (image[i][j] !== undefined && image[i][j+2] !== undefined && image[i+2]) {
let sum = 0
for (let k = 0; k < SQUARE; k++) {
for (let y = 0; y < SQUARE; y++) {
sum += image[i+k][j+y]
}
}
innerArr.push(Math.floor(sum/9))
}
}
if (innerArr.length) outerArr.push(innerArr)
}
return outerArr
}
```
3 Answers 3
Some suggestions on coding style:
Your choice in variables is somewhat confusing
i
,j
? For an algorithm operating on a 2D image, the namesx
andy
would be better. Anddx
,dy
for the offsets in the inner loop.You defined the width of your kernel as
SQUARE
, yet you hardcode2
in the expressionimage[i][j+2]
.Your boundary checks can be more efficient. Why check for
undefined
if you know the exact size of the image? You can loopi
from0
toimage.length - SQUARE
, and loopj
from0
toimage[0].length - SQUARE
and remove the if statement:
for (let i = 0; i < image.length - SQUARE; i++) {
for (let j = 0; j < image[0].length - SQUARE; j++) {
// no if-statement needed anymore
...
}
}
Note that the naive algorithm works fine for small kernels, but can be done more efficient using a summation map where you first calculate the total sum of all preceding values in a preprocessing step, and then calculate the sum of the pixels in the square using the summation map of the 4 corner points. That way, the algorithm speed becomes independent of the size SQUARE
of your kernel.
-
\$\begingroup\$ very helpful. I initially checked for
undefined
as some numbers in theimage
can evaluate to0
; the if statement would fail then. Regarding your third bullet point, I couldn't really understand it. Could you show me a bit of code? \$\endgroup\$uber– uber2021年05月13日 11:42:11 +00:00Commented May 13, 2021 at 11:42 -
1\$\begingroup\$ @uber Added example code. By reducing the range of the
i
andj
variables, the innerk
andy
loops will never exceed the boundaries so you can remove the if-statement. \$\endgroup\$Jason Smith– Jason Smith2021年05月13日 16:15:37 +00:00Commented May 13, 2021 at 16:15
The code in general is pretty good, so I don't have a lot to suggest, just a handful of pointers.
- As already mentioned, variable naming could be a little better. For example, I'm not sure why your two most inner loops use the variables "k" and "y". "SQUARE" would probably be better named as "SQUARE_SIZE". outerArr and innerArr could be newImage and newRow. etc.
- Four nested loops is quite a lot. You could easily extract the two most inner loops into its own "calcBlurValueAtCoord()" function.
- When checking for out-of-bounds, the
image[i][j] !== undefined
check is doing nothing -image[i][j]
will never be undefined. In fact, that wholeif()
could be simplified to this:if (image[i+2]?.[j+2] !== undefined)
Once you've applied those bullet points, I think your code would be pretty solid.
If you additionally wanted to make the code a little more functional and less procedural (not that you should or shouldn't, it's just a different style), you could replace the for loops with functions such as .map()
and break out more helper functions.
In the below example, you'll notice that the boxBlur() function is really high-level - just by reading it, you can get an overview of how a box-blur is done: "With each coordinate in the grid, calculate a blur value, then trim the margins". What does it mean to "calculate a blur value"? Just look at that function definition (for blurValueAtCoord()): "Take the average of the surrounding coordinates and floor it" - you get the idea.
const boxBlur = grid => trimMargins(
coordsInGrid(grid)
.map(coord => blurValueAtCoord(grid, coord))
)
const average = array => array.reduce((a, b) => a + b) / array.length
const coordsInGrid = grid => grid.flatMap((row, y) => row.map((_, x) => [x, y]))
const blurValueAtCoord = (grid, coord) => Math.floor(average(getSurroundingValues(grid, coord)))
const trimMargins = grid => grid.slice(1, -1).map(row => row.slice(1, -1))
const getSurroundingValues = (grid, [y, x]) => [
grid[y-1]?.[x-1], grid[y]?.[x-1], grid[y+1]?.[x-1],
grid[y-1]?.[x], grid[y]?.[x], grid[y+1]?.[x],
grid[y-1]?.[x+1], grid[y]?.[x+1], grid[y+1]?.[x+1],
].map(x => x ?? 0)
-
\$\begingroup\$ loved the if statement simplification \$\endgroup\$uber– uber2021年05月13日 11:48:46 +00:00Commented May 13, 2021 at 11:48
Here is a solution without an if statement as suggested by Jason
I removed the if statement, I used dx dy to represent the offset of x and y respectively
I didn't see the need to initiate Square variable to 3, hence I -1 so I can trim the borders
Hope this helps someone
function boxBlur(image){
let blur = [];
for (let x = 1; x < image.length - 1; x++){
Let line = [];
for (let y = 1; x < image[0].length - 1; y++){
let sum = 0;
//slicing off the borders of the image
for (let dx = -1; dx <= 1; dx++){
for (let dy = -1; dx <= 1; dy++){
sum+= image[x + dx][y + dy]
}
}
line.push(Math.floor(sum/9));
}
blur.push(line);
}
return blur
}
-
\$\begingroup\$ Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center. \$\endgroup\$2022年10月23日 10:30:27 +00:00Commented Oct 23, 2022 at 10:30
-
\$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but there's very little actual review here. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$Toby Speight– Toby Speight2022年10月23日 11:47:12 +00:00Commented Oct 23, 2022 at 11:47