How Many Holes?

MATLAB / Octave, 18 bytes

@(g)1-bweuler(g,4)

Try it online!

This is an anonymous function taking a logical matrix as input. The object is formed by the true entries (with the specified connectivity), the empty space are the false entries.

bweuler then calculates the euler number of the binary image represented by that matrix, that is the number of objects minus the number of holes.


Mathematica, 59 57 bytes

1/.ComponentMeasurements[#,"Holes",CornerNeighbors->0>1]&

There's a built-in for that. Takes input as a 2D matrix of 1s (walls) and 0s (holes). For convenience, here are all the test cases in this input format:

{{{1,1,1,1},{1,0,0,1},{1,0,0,1},{1,1,1,1}},
 {{1,1,1,1},{1,0,0,1},{1,0,1,1},{1,1,1,0}},
 {{1,1,1,1,1},{1,0,1,0,1},{1,0,0,0,1},{1,1,1,1,1}},
 {{1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1},{1,0,0,0,1,1,1,1},{1,0,0,0,1,1,1,1},{1,0,1,1,1,1,1,1},{1,0,0,0,0,0,0,0},{1,1,1,1,1,1,1,1}},
 {{1,1,1},{1,0,0},{1,1,1}},
 {{1,1,1,1,1,1,1,1,1,1},{1,0,0,0,0,0,0,0,0,0},{1,0,1,1,1,1,1,1,1,1},{1,0,1,0,0,0,0,0,0,1},{1,0,1,0,1,1,1,1,0,1},{1,0,1,0,0,0,1,1,0,1},{1,0,1,1,1,1,1,1,0,1},{1,0,0,0,0,0,0,0,0,1},{1,1,1,1,1,1,1,1,1,1}},
 {{1,1,1,1,1},{1,0,1,0,1},{1,1,1,1,1}},
 {{1,1,1,1,1},{1,0,0,0,0},{1,0,1,1,1},{1,0,1,0,1},{1,1,1,1,1}},
 {{1,1,1,1},{1,1,0,1},{1,0,1,1},{1,1,1,1}}}

Alternative solution, 59 bytes

This was my original approach. It's also based on the component-related built-ins, but doesn't count the holes directly (instead it treats the holes themselves as components).

Max@*MorphologicalComponents@*DeleteBorderComponents@*Image

Takes the same input format as above, but with the roles of 0s and 1s swapped.

The reason I need to convert this to an Image first is that, otherwise, Mathematica would consider all the 1-cells as part of a single component (because it treats the matrix as a component-label matrix). Hence, if any 1-cell borders the margin, it would delete all of them. When DeleteBorderComponents is used on an image instead, then it will do an implicit connectivity check to find the components.

Alternatively, I could call MorphologicalComponents first, which would turn the input into a suitable label matrix, but if I do DeleteBorderComponents second it's no longer guaranteed that the maximum component label corresponds to the number of components (because I might delete a smaller component).


Python 2, 282 bytes

+100 to handle diagonal touches TT_TT (do we really need that?)
-119 thanks to @Rod guidance :)

Try it online. Takes array of arrays of chars '#' and whitespace as input.

A=input()
c=0
X=len(A[0])-1
Y=len(A)-1
def C(T):
 x,y=T
 global g
 if A[y][x]<'#':
    if y<1or y==Y or x<1or x==X:g=0
    A[y][x]='#';map(C,zip([x]*3+[min(x+1,X)]*3+[max(x-1,0)]*3,[y,min(y+1,Y),max(y-1,0)]*3))
while' 'in sum(A,[]):i=sum(A,[]).index(' ');g=1;C((i%-~X,i/-~X));c+=g
print c

Searches for first whitespace and mark it as non-empty ('#'). Recursively check all of it's surrounding, while filling all empty cells. If any empty cell of current "hole" appears to be on border counter won't change, otherwise it would be increased by 1. Repeat process, until there is no more whitespaces.