I started by creating two positive test cases for our check
function -
const puzzle1 =
[ [" ", " ", "#", "#", "#", " ", " ", " ", " ", " "]
, [" ", " ", "#", " ", "#", " ", " ", " ", " ", " "]
, [" ", " ", "#", "#", "#", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
]
const puzzle2 =
[ [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", " ", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
]
console.log(check(puzzle1))
// expected: [ 0, 2 ]
console.log(check(puzzle2))
// expected: [ 3, 4 ]
And a negative case -
const puzzle3 =
[ [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
]
console.log(check(puzzle3))
// expected: false
First I implemented cross
and blank
to handle the subproblems more easily -
const cross = x =>
x === "#"
const blank = x =>
x === " "
Now we implement check
-
const check =
( [ [ a, b, c, ...r0 ] = []
, [ d, e, f, ...r1 ] = []
, [ g, h, i, ...r2 ] = []
, ...rest
]
, row = 0
, col = 0
) =>
// bottom-right is out of bounds
i === undefined
? false
// perimeter is cross and center is blank
// solution found: return the row and col
: [ a, b, c, d, f, g, h, i ] .every (cross) && blank (e)
? [ row, col ]
// otherwise check the next column
: check
( [ [ b, c, ...r0 ]
, [ e, f, ...r1 ]
, [ h, i, ...r2 ]
, ...rest .map (([ _, ...row ]) => row)
]
, row
, col + 1
)
|| // or check the next row
check
( [ [ d, e, f, ...r1 ]
, [ g, h, i, ...r2 ]
, ...rest
]
, row + 1
, col
)
I like this solution because we can see visually what's going on in the check -
// (a b c f i h g d) makes a "donut" shape
// (e) is the donut hole
[ [ a, b, c, ...r0 ] = []
, [ d, e, f, ...r1 ] = []
, [ g, h, i, ...r2 ] = []
, ...rest
]
// how am I supposed to visualize this?
arr[row][col] == "#"
arr[row+1][col] == "#"
arr[row+2][col] == "#"
arr[row][col+1] == "#"
arr[row+1][col+1] == " "
arr[row+2][col+1] == "#"
arr[row][col+2] == "#"
arr[row+1][col+2] == "#"
arr[row+2][col+2] == "#"
Using nested for
loops constrains us to thinking about the problem using indices and performing manual look-ups like arr[row+1][col+2]
. Thinking at this level of granularity is fatiguing on the brain and prone to producing erroneous programs. By contrast, use of deep destructuring and recursion allows our program to mirror the shape of its data and free us from complexity.
Expand the program below to verify the results in your own browser -
const cross = x =>
x === "#"
const blank = x =>
x === " "
const check =
( [ [ a, b, c, ...r0 ] = []
, [ d, e, f, ...r1 ] = []
, [ g, h, i, ...r2 ] = []
, ...rest
]
, row = 0
, col = 0
) =>
i === undefined
? false
: [ a, b, c, d, f, g, h, i ] .every (cross) && blank(e)
? [ row, col ]
: check
( [ [ b, c, ...r0 ]
, [ e, f, ...r1 ]
, [ h, i, ...r2 ]
, ...rest .map (([ _, ...row ]) => row)
]
, row
, col + 1
)
||
check
( [ [ d, e, f, ...r1 ]
, [ g, h, i, ...r2 ]
, ...rest
]
, row + 1
, col
)
const puzzle1 =
[ [" ", " ", "#", "#", "#", " ", " ", " ", " ", " "]
, [" ", " ", "#", " ", "#", " ", " ", " ", " ", " "]
, [" ", " ", "#", "#", "#", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
]
const puzzle2 =
[ [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", " ", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
]
const puzzle3 =
[ [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", "#", "#"]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", "#", "#", "#", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
, [" ", " ", " ", " ", " ", " ", " ", " ", " ", " "]
]
console.log(check(puzzle1))
// [ 0, 2 ]
console.log(check(puzzle2))
// [ 3, 4 ]
console.log(check(puzzle3))
// false