解决来自 Google Code Jam 的问题(2009.1AA:“多基幸福”)我想出了一个尴尬的(代码方面的)解决方案,我对如何改进它很感兴趣。
简而言之,问题描述是:对于给定列表中的所有碱基,找到大于 1 的最小数字,其迭代计算数字平方和达到 1。
或伪Haskell中的描述(如果elem
总是适用于无限列表,则可以解决它的代码):
solution =
head . (`filter` [2..]) .
all ((1 `elem`) . (`iterate` i) . sumSquareOfDigitsInBase)
还有我尴尬的解决方案:
- 尴尬我的意思是它有这样的代码:
happy <- lift . lift . lift $ isHappy Set.empty base cur
- 我记住了 isHappy 函数的结果。将 State monad 用于记忆结果 Map。
- 试图找到第一个解决方案,我没有使用
head
andfilter
(就像上面的伪 Haskell 一样),因为计算不是纯粹的(改变状态)。因此,我通过使用带有计数器的 StateT 和 MaybeT 进行迭代,以在条件成立时终止计算。 - 已经在 a
MaybeT (StateT a (State b))
中,如果条件不适用于一个基数,则无需检查其他基数,因此我MaybeT
在堆栈中有另一个基数。
代码:
import Control.Monad.Maybe
import Control.Monad.State
import Data.Maybe
import qualified Data.Map as Map
import qualified Data.Set as Set
type IsHappyMemo = State (Map.Map (Integer, Integer) Bool)
isHappy :: Set.Set Integer -> Integer -> Integer -> IsHappyMemo Bool
isHappy _ _ 1 = return True
isHappy path base num = do
memo <- get
case Map.lookup (base, num) memo of
Just r -> return r
Nothing -> do
r <- calc
when (num < 1000) . modify $ Map.insert (base, num) r
return r
where
calc
| num `Set.member` path = return False
| otherwise = isHappy (Set.insert num path) base nxt
nxt =
sum . map ((^ (2::Int)) . (`mod` base)) .
takeWhile (not . (== 0)) . iterate (`div` base) $ num
solve1 :: [Integer] -> IsHappyMemo Integer
solve1 bases =
fmap snd .
(`runStateT` 2) .
runMaybeT .
forever $ do
(`when` mzero) . isJust =<<
runMaybeT (mapM_ f bases)
lift $ modify (+ 1)
where
f base = do
cur <- lift . lift $ get
happy <- lift . lift . lift $ isHappy Set.empty base cur
unless happy mzero
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"
main :: IO ()
main =
getContents >>=
putStr . solve . tail . lines
其他使用 Haskell 的参赛者确实有更好的解决方案,但解决问题的方式不同。我的问题是关于对我的代码进行小的迭代改进。