bmCreate :: Int -> BitMask bmLength :: BitMask -> Int bmSize :: BitMask -> Int bmIsEmpty :: BitMask -> Bool bmNot :: BitMask -> BitMask bmAnd :: BitMask -> BitMask -> BitMask bmOr :: BitMask -> BitMask -> BitMask bmIntersection :: [BitMask] -> BitMask bmUnion :: [BitMask] -> BitMask bmSplit :: BitMask -> [BitMask] bmByOne :: BitMask -> [BitMask] bmExpand :: BitMask -> BitMask bmFillGaps :: BitMask -> BitMask bmLeftIncursion :: Int -> BitMask -> BitMask bmRightIncursion :: Int -> BitMask -> BitMask bmTranspose :: [BitMask] -> [BitMask]
bmLeftIncursion
and bmRightIncursion
. Why they are needed, it will be clear later, the principle of their work is the following: bmLeftIncursion
finds the leftmost filled bit and creates a mask in which all the bits before it are filled, as well as so many bits since it, as specified when calling the function; the second function works the same way. data Field = Field { flHorLines :: [Line], flVerLines :: [Line] } deriving Eq
data Line = Line { lnMask :: LineMask, lnBlocks :: [Block] } deriving Eq
data LineMask = LineMask { lmFilledMask :: BitMask, lmBlockedMask :: BitMask } deriving Eq
data Block = Block { blScopeMask :: BitMask, blNumber :: Int } deriving Eq
BitMask
) are instances of two classes: Completable
and Syncable
.Completable
class is showing the "completeness" of an object. A field is considered complete if all its lines are completed. A line is completed if all its blocks are completed; the completeness of the mask is unnecessary to demand (it follows from the completeness of the blocks; why, again, it will be clear a little later). To complete a block, as mentioned above, it is necessary that the size of its area coincides with its number. class Completable a where clIsCompleted :: a -> Bool instance Completable Field where clIsCompleted fl = all clIsCompleted (flHorLines fl) && all clIsCompleted (flVerLines fl) instance Completable Line where clIsCompleted ln = all clIsCompleted (lnBlocks ln) instance Completable Block where clIsCompleted bl = bmSize (blScopeMask bl) == blNumber bl
Syncable
class provides functions that allow you to bring together different solution branches. snAverage
distinguishes from two branches only the general, and snSync
- that manifested itself in at least one branch (we can consider them as generalizations of the bmAnd
and bmOr
respectively). snAverageAll
and snSyncAll
do exactly the same thing, but they work not with two objects, but with lists of objects. class Syncable a where snSync :: a -> a -> Maybe a sn1 `snSync` sn2 = snSyncAll [sn1, sn2] snAverage :: a -> a -> Maybe a sn1 `snAverage` sn2 = snAverageAll [sn1, sn2] snSyncAll :: [a] -> Maybe a snSyncAll [] = Nothing snSyncAll sns = foldr1 (wrap snSync) (map return sns) snAverageAll :: [a] -> Maybe a snAverageAll [] = Nothing snAverageAll sns = foldr1 (wrap snAverage) (map return sns) wrap :: Monad m => (a -> b -> mc) -> ma -> mb -> mc wrap f mx my = do x <- mx y <- my fxy
Syncable
class Syncable
clear that their result is an object wrapped in the Maybe
monad. In fact, this is how the important concept of consistency manifests itself, which is also defined for all the above types, but not in a separate class for reasons of encapsulation. As an example, the same cell cannot be simultaneously painted over and blocked; if any operation can lead to such a situation, then it is labeled with the Maybe
monad (usually has the type type TransformFunction a = a -> Maybe a
), and if it leads to this situation, then the result is Nothing
, because no object in the program can exist in an inconsistent state. Since Nothing
, in turn, cannot be a component of other objects, the entire field will become inconsistent, which will mean the absence of solutions. flEnsureConsistency :: TransformFunction Field flEnsureConsistency fl = do let lnsHor = flHorLines fl let lnsVer = flVerLines fl lnsHor' <- zipWithM lnSyncWithLineMask (lmTranspose $ map lnMask lnsVer) lnsHor lnsVer' <- zipWithM lnSyncWithLineMask (lmTranspose $ map lnMask lnsHor) lnsVer return $ Field lnsHor' lnsVer' lnSyncWithLineMask :: LineMask -> TransformFunction Line lnSyncWithLineMask lm ln = do lm' <- lm `snSync` lnMask ln return ln { lnMask = lm' }
blEnsureConsistency :: TransformFunction Block blEnsureConsistency bl = do let bms = filter ((blNumber bl <=) . bmSize) $ bmSplit $ blScopeMask bl guard $ not $ null bms return bl { blScopeMask = bmUnion bms }
lmEnsureConsistency :: TransformFunction LineMask lmEnsureConsistency lm = do guard $ bmIsEmpty $ lmFilledMask lm `bmAnd` lmBlockedMask lm return lm
lmFill :: BitMask -> TransformFunction LineMask lmFill bm lm = lmEnsureConsistency lm { lmFilledMask = lmFilledMask lm `bmOr` bm } lmBlock :: BitMask -> TransformFunction LineMask lmBlock bm lm = lmEnsureConsistency lm { lmBlockedMask = lmBlockedMask lm `bmOr` bm } blExclude :: BitMask -> TransformFunction Block blExclude bm bl = blEnsureConsistency $ bl { blScopeMask = blScopeMask bl `bmAnd` bmNot bm } blKeep :: BitMask -> TransformFunction Block blKeep bm bl = blEnsureConsistency $ bl { blScopeMask = blScopeMask bl `bmAnd` bm }
lnUpdateBlocked :: [Block] -> TransformFunction LineMask lnUpdateBlocked [] lm = lmBlock (bmNot $ lmBlockedMask lm) lm lnUpdateBlocked bls lm = lmBlock (bmNot $ bmUnion $ map blScopeMask bls) lm
blToFillMask
function, blToFillMask
can get a mask that you need to paint over. It is the intersection of two masks, obtained by “driving” the block into the leftmost and rightmost parts of its area. blMinimumLeftMask :: Block -> BitMask blMinimumLeftMask bl = bmLeftIncursion (blNumber bl) (blScopeMask bl) blMinimumRightMask :: Block -> BitMask blMinimumRightMask bl = bmRightIncursion (blNumber bl) (blScopeMask bl) blToFillMask :: Block -> BitMask blToFillMask bl = blMinimumLeftMask bl `bmAnd` blMinimumRightMask bl lnUpdateFilled :: [Block] -> TransformFunction LineMask lnUpdateFilled [] = return lnUpdateFilled bls = lmFill (bmUnion $ map blToFillMask bls)
bmLeftIncursion
and bmRightIncursion
. Strictly speaking, if they were used only for this purpose, then most likely they would have looked a little different, namely they would not fill the bit mask to the very first bit the original mask.) lnEnsureConsistency :: TransformFunction Line lnEnsureConsistency ln = do let bls = lnBlocks ln lm <- lnUpdateBlocked bls >=> lnUpdateFilled bls $ lnMask ln return $ ln { lnMask = lm }
lnRemoveBlocked :: LineMask -> TransformFunction [Block] lnRemoveBlocked = mapM . blExclude . lmBlockedMask
lnRemoveFilled :: LineMask -> TransformFunction [Block] lnRemoveFilled lm = mapM (\ bl -> foldM f bl $ bmSplit $ lmFilledMask lm) where f bl bm = if blCanContainMask bm bl then return bl else blExclude (bmExpand bm) bl blCanContainMask :: BitMask -> Block -> Bool blCanContainMask bm bl = let bm' = bmFillGaps bm in bmSize bm' <= blNumber bl && bmIsEmpty (bm' `bmAnd` bmNot (blScopeMask bl))
blMinimumLeftMask
its left neighbor and blMinimumRightMask
right neighbor should be excluded from the area of each block (here they are needed exactly in the form described above). To be precise, these masks are expanded by one cell, since there must be at least one empty cell between the blocks. lnExcludeNeighbours :: TransformFunction [Block] lnExcludeNeighbours bls = sequence $ scanr1 (flip $ wrap $ blExclude . bmExpand . blMinimumRightMask) $ scanl1 (wrap $ blExclude . bmExpand . blMinimumLeftMask) $ map return bls
slLoop
function will be described later): lnSimpleTransform :: TransformFunction Line lnSimpleTransform ln = do let lm = lnMask ln bls <- lnRemoveBlocked lm >=> slLoop (lnRemoveFilled lm >=> lnExcludeNeighbours) $ lnBlocks ln lnEnsureConsistency ln { lnBlocks = bls }
lnExtremeOwners :: BitMask -> TransformFunction [Block] lnExtremeOwners bm bls = do bls' <- fmap reverse $ maybe (return bls) (f bmLeftIncursion bls) (h bls) fmap reverse $ maybe (return bls') (f bmRightIncursion bls') (h bls') where fg = varyNth (\ bl -> blKeep (g (blNumber bl) bm) bl) h = findIndex (blCanContainMask bm) varyNth :: Monad m => (a -> ma) -> [a] -> Int -> m [a] varyNth f xs idx = do let (xs1, x : xs2) = splitAt idx xs x' <- fx return $ xs1 ++ x' : xs2
lnTransformByExtremeOwners :: TransformFunction Line lnTransformByExtremeOwners ln = do bls <- foldM (flip lnExtremeOwners) (lnBlocks ln) $ bmSplit $ lmFilledMask $ lnMask ln lnEnsureConsistency ln { lnBlocks = bls }
flTransformByLines :: TransformFunction Line -> TransformFunction Field flTransformByLines f fl = do lnsHor <- mapM f (flHorLines fl) fl' <- flEnsureConsistency fl { flHorLines = lnsHor } lnsVer <- mapM f (flVerLines fl') flEnsureConsistency fl' { flVerLines = lnsVer }
type ForkFunction a = a -> [[a]]
, where the internal list includes mutually exclusive options, and the external list contains various ways to produce these options. lnForkByCells :: ForkFunction Line lnForkByCells ln = do let lm = lnMask ln bm <- bmByOne $ lmEmptyMask lm return $ do lm' <- [fromJust $ lmBlock bm lm, fromJust $ lmFill bm lm] maybeToList $ lnEnsureConsistency ln { lnMask = lm' } flForkByCells :: ForkFunction Field flForkByCells fl = do let lnsHor = flHorLines fl let lnsVer = flVerLines fl idx <- findIndices (not . clIsCompleted) lnsHor let (lns1, ln : lns2) = splitAt idx lnsHor lns <- lnForkByCells ln return $ do ln' <- lns maybeToList $ flEnsureConsistency $ Field (lns1 ++ ln' : lns2) lnsVer
lnForkByOwners :: ForkFunction Line lnForkByOwners ln = do let bls = lnBlocks ln bm <- bmSplit $ lmFilledMask $ lnMask ln case findIndices (blCanContainMask bm) bls of [_] -> [] idxs -> return $ do idx <- idxs maybeToList $ do bls' <- varyNth (g bm) bls idx lnEnsureConsistency ln { lnBlocks = bls' } where g bm bl = blKeep ((bmAnd `on` ($ bm) . ($ blNumber bl)) bmLeftIncursion bmRightIncursion) bl
slLoop :: Eq a => TransformFunction a -> TransformFunction a slLoop fx = do x' <- fx if x == x' then return x else slLoop fx' slSmartLoop :: (Completable a, Eq a) => TransformFunction a -> TransformFunction a slSmartLoop fx | clIsCompleted x = return x | otherwise = do x' <- fx if x == x' then return x else slLoop fx'
slForkAndSyncAll :: (Syncable a) => ForkFunction a -> TransformFunction a -> TransformFunction a slForkAndSyncAll fgx = do xs <- mapM (snAverageAll . mapMaybe g) $ fx snSyncAll (x : xs) slForkAndSmartSync :: (Syncable a, Completable a, Eq a) => ForkFunction a -> TransformFunction a -> TransformFunction a slForkAndSmartSync fgx = foldr h (return x) (fx) where h xs mx = do x' <- mx if clIsCompleted x' then mx else case mapMaybe (snSync x') xs of [] -> Nothing xs' -> case filter (/= x') xs' of [] -> return x' xs'' -> snAverageAll . mapMaybe g $ xs''
slAllSolutions :: (Completable a) => ForkFunction a -> TransformFunction a -> a -> [a] slAllSolutions fgx = do x' <- maybeToList $ gx if clIsCompleted x' then return x' else case fx' of (xs : _) -> do x'' <- xs slAllSolutions fg x'' [] -> []
lineTransform = slSmartLoop $ lnSimpleTransform >=> lnTransformByExtremeOwners
lineTransform' = slForkAndSyncAll lnForkByOwners lineTransform
fieldTransform = slSmartLoop $ slSmartLoop (flTransformByLines lineTransform) >=> flTransformByLines lineTransform'
fieldTransform' = slForkAndSmartSync flForkByCells fieldTransform
fieldTransform'' = slSmartLoop $ fieldTransform >=> fieldTransform'
solve = slAllSolutions flForkByCells fieldTransform''
Source: https://habr.com/ru/post/151819/
All Articles