Haskell 单子变压器组
解决GoogleCodeJam()中的一个问题我提出了一个笨拙的(代码方面的)解决方案,我对如何改进它感兴趣 简而言之,问题描述是:对于给定列表中的所有基,找到大于1的最小数,对于该数,迭代计算的数字平方和达到1 或pseudo Haskell中的描述(如果Haskell 单子变压器组,haskell,monad-transformers,Haskell,Monad Transformers,解决GoogleCodeJam()中的一个问题我提出了一个笨拙的(代码方面的)解决方案,我对如何改进它感兴趣 简而言之,问题描述是:对于给定列表中的所有基,找到大于1的最小数,对于该数,迭代计算的数字平方和达到1 或pseudo Haskell中的描述(如果elem可以始终用于无限列表,则可以解决此问题的代码): 我尴尬的解决方案是: 我的意思是它有这样的代码:happy Integer->Integer->IsHappyMemo Bool isHappy_uu1=返回真值 isHappy路径
elem
可以始终用于无限列表,则可以解决此问题的代码):
我尴尬的解决方案是:
- 我的意思是它有这样的代码:
happy Integer->Integer->IsHappyMemo Bool isHappy_uu1=返回真值 isHappy路径基数=do 备忘录回执 无事可做 r IsHappyMemo整数 溶解1碱基= fmap snd。 (`runStateT`2)。 伦梅贝特。 永远$do
(`when`mzero)。isJust=Monad*类的存在是为了消除重复提升的需要。如果您这样更改签名:type IsHappyMemo = Map.Map (Integer, Integer) Bool isHappy :: MonadState IsHappyMemo m => Set.Set Integer -> Integer -> Integer -> m Bool
solve1 bases = do result:_ <- filterM cond [2..] return result where cond num = fmap and . mapM (isHappy Set.empty num) bases
这样你就可以移除大部分的“升降机”。但是,不能删除最长的提升序列,因为它是StateT中的状态monad,因此使用MonadState类型类将为您提供外部StateT,您需要在其中进入内部状态。您可以将您的状态monad封装在一个新类型中,并创建一个MonadHappy类,类似于现有的monad类。您的解决方案在使用(和滥用)monad时肯定很笨拙:- 通常,通过堆叠几个变压器来构建单子
- 堆叠多个状态的情况不太常见,但有时仍然会发生
- 堆叠几个变压器是非常罕见的
- 使用MaybeT中断循环更不寻常
现在我们将重点放在函数solve1上,让我们简化它。 一个简单的方法是移除内部的MaybeT单子。当一个快乐的数字被发现时,你可以用另一种方法来代替一个永远的循环,只有当 数字是不快乐的 而且,你也不需要国家单子,是吗?总是可以用显式参数替换状态 应用这些想法solve1现在看起来好多了:(`when` mzero) . isJust =<< runMaybeT (mapM_ f bases)
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)
我会对这个代码更满意。 您的解决方案的其余部分很好。 让我烦恼的一件事是,你把每个子问题的备忘录缓存都扔掉了。这有什么原因吗solve1::[Integer]->IsHappyMemo Integer solve1基地=去哪里 go i=do happyBases isHappy Set.empty b i)base 如果和快乐 然后我回来 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 解决= 康卡特。 (`evalState`Map.empty)。 mapM f。 zip[1::整数..] 哪里 f(idx,prob)=do s
(来自软件包)在必要时停止计算方面比ListT
做得好得多MaybeT
solve1 :: [Integer] -> IsHappyMemo Integer solve1 bases = do Cons result _ <- runList . filterL cond $ fromList [2..] return result where cond num = andL . mapL (isHappy Set.empty num) $ fromList bases
solve1::[Integer]->IsHappyMemo Integer solve1碱基=do
反对结果u@peiborra:答案很好,但我认为存在一些问题。我知道我不会像你说的那样扔掉备忘录。您可以返回“solutions”而不是“unlines solutions”,并将“unlines”应用于evalState的结果。那就不做了“解决方案是正确的,我没有给予足够的注意,您的代码没有丢弃备忘录缓存。规则。所有基,状态monad在默认情况下都是惰性的,因此只应计算真正需要的基。@peiborra:reg。所有的基础,我不认为国家单子的懒散解决了它。这仍然会影响状态,在下一次需要状态时(下一次调用solve1),需要进行这些计算来计算它。@yairchu这里有两件事。第一,由于默认状态monad是惰性的,因此计算(更新状态)的效果将暂停,直到真正需要为止。在本例中,在下一次为solve1开票时,实际上需要状态,因此,是的,其余基中的那些isHappy调用都会发生。但是第二,由于状态本身是惰性的,因为Data.Map在值方面是惰性的(我相信在键方面也是严格的),那些对isHappy的额外调用被挂起,除非真的需要,否则永远不会有机会运行。@yairchu另一次尝试,这次是用代码:你能解释更多,如何“停止”计算吗?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
solve1 :: [Integer] -> IsHappyMemo Integer solve1 bases = do Cons result _ <- runList . filterL cond $ fromList [2..] return result where cond num = andL . mapL (isHappy Set.empty num) $ fromList bases
solve1 bases = do result:_ <- filterM cond [2..] return result where cond num = fmap and . mapM (isHappy Set.empty num) bases