Your solution is, of course, inconvenient to use (and abuse) monads:
- Usually build monads in parts by stacking several transformers.
- This is a less common occurrence, but it sometimes happens that for a stack of several states
- It is very difficult to stack several transformers. Maybe
- Itβs even more unusual to use MaybeT to break the loop.
Your code is too pointless:
(`when` mzero) . isJust =<< runMaybeT (mapM_ f bases)
instead of simplified reading
let isHappy = isJust $ runMaybeT (mapM_ f bases) when isHappy mzero
Now let's focus on solve1 function, simplify it. An easy way to do this is to remove the MaybeT internal nun. Instead of a continuous loop that breaks when a lucky number is found, you can go the other way around and recurs only if the number is not satisfied.
In addition, you also do not need a state monad, do you? You can always replace a state with an explicit argument.
Applying these solve1 ideas now looks much better:
solve1 :: [Integer] -> IsHappyMemo Integer solve1 bases = go 2 where go i = do happyBases <- mapM (\b -> isHappy Set.empty bi) bases if and happyBases then return i else go (i+1)
I would be happier with this code. The rest is your decision. My concern is that you throw away the memo cache for each subtask. Is there a reason for this?
solve :: [String] -> String solve = concat . (`evalState` Map.empty) . mapM f . zip [1 :: Integer ..] where f (idx, prob) = do s <- solve1 . map read . words $ prob return $ "Case #" ++ show idx ++ ": " ++ show s ++ "\n"
Wouldn't your solution be more effective if you reused it?
solve :: [String] -> String solve cases = (`evalState` Map.empty) $ do solutions <- mapM f (zip [1 :: Integer ..] cases) return (unlines solutions) where f (idx, prob) = do s <- solve1 . map read . words $ prob return $ "Case #" ++ show idx ++ ": " ++ show s