-- editorial

import Control.Monad(join, replicateM)
import qualified Data.Vector as U
import qualified Data.ByteString.Char8 as C
import qualified Data.Array.IO as A

type Field = A.IOArray (Int,Int) Char

main = do
    [h,w] <- map read . words <$> getLine :: IO [Int]
    arr <- A.newArray ((1,1),(h,w)) '.' :: IO Field
    iniBks <- readBks h w
    writeBks arr iniBks
    res <- solve arr iniBks 0
    print res

solve :: Field -> U.Vector (Int,Int) -> Int -> IO Int
solve arr bks n = do
    nBks <- nextBks arr bks
    if U.null nBks then return n
        else do
            writeBks arr nBks
            solve arr nBks (n+1)



writeBks :: Field -> U.Vector (Int,Int) -> IO ()
writeBks arr = U.mapM_ (\c-> A.writeArray arr c '#')


nextBks :: Field -> U.Vector (Int,Int) -> IO (U.Vector (Int,Int))
nextBks arr oldBks = do
    cellBks <- U.mapM (checkNbs arr) oldBks
    return $ join cellBks

checkNbs :: Field -> (Int,Int) -> IO (U.Vector (Int,Int))
checkNbs arr coord = do
    bounds <- A.getBounds arr
    let nbs = neighbours coord bounds
    let check :: (Int,Int) -> IO Bool
        check nb = A.readArray arr nb >>= (return . (=='.'))
    U.filterM check nbs



-- read

readBS :: C.ByteString -> Maybe (Char, C.ByteString)
readBS str
    | C.null str = Nothing
    | otherwise = Just (C.head str, C.tail str)

unpackV :: Int -> C.ByteString -> U.Vector Char
unpackV n str = U.unfoldrN n readBS str

readBks :: Int -> Int -> IO (U.Vector (Int,Int))
readBks h w = do
    linVecs <- unpackV (h*w) . C.concat <$> replicateM h (C.getLine)
    let assocs = U.zip (U.fromList $ A.range ((1,1),(h,w))) linVecs
    return $ U.map fst . U.filter ( (=='#') . snd) $ assocs





neighbours :: (Int,Int) -> ((Int,Int),(Int,Int)) -> U.Vector (Int,Int)
neighbours (x,y) ((x1,y1),(x2,y2)) =
    let a = if x > x1 then (U.cons (x-1,y)) else id
        b = if y > y1 then (U.cons (x,y-1)) else id
        c = if x < x2 then (U.cons (x+1,y)) else id
        d = if y < y2 then (U.cons (x,y+1)) else id
    in (a.b.c.d) U.empty
