Ваше решение, конечно, неудобно в использовании (и злоупотреблении) монад:
- Обычно монады строят по частям, складывая несколько трансформаторов
- Это менее обычно, но иногда случается, чтобы сложить несколько состояний
- Очень необычно сложить несколько трансформаторов Maybe
- Еще более необычно использовать MaybeT для прерывания цикла
Ваш код слишком бессмысленный:
(`when` mzero) . isJust =<<
runMaybeT (mapM_ f bases)
вместо более простого для чтения
let isHappy = isJust $ runMaybeT (mapM_ f bases)
when isHappy mzero
Теперь остановимся на функции solve1, давайте упростим ее.
Самый простой способ сделать это - удалить внутреннюю монаду MaybeT. Вместо вечного цикла, который прерывается, когда найдено счастливое число, вы можете пойти другим путем и выполнить рекурсию только в том случае, если
номер не радует.
Более того, вам не нужна государственная монада, не так ли? Всегда можно заменить состояние явным аргументом.
Используя эти идеи, решение execute1 теперь выглядит намного лучше:
solve1 :: [Integer] -> IsHappyMemo Integer
solve1 bases = go 2 where
go i = do happyBases <- mapM (\b -> isHappy Set.empty b i) bases
if and happyBases
then return i
else go (i+1)
Я был бы более счастлив с этим кодом.
В остальном ваше решение в порядке.
Меня беспокоит то, что вы выбрасываете кэш заметок для каждой подзадачи. Есть ли причина для этого?
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"
Разве ваше решение не будет более эффективным, если вы будете использовать его повторно?
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