Haskell 单子变压器组

Haskell 单子变压器组,haskell,monad-transformers,Haskell,Monad Transformers,解决GoogleCodeJam()中的一个问题我提出了一个笨拙的(代码方面的)解决方案,我对如何改进它感兴趣 简而言之,问题描述是:对于给定列表中的所有基,找到大于1的最小数,对于该数,迭代计算的数字平方和达到1 或pseudo Haskell中的描述(如果elem可以始终用于无限列表,则可以解决此问题的代码): 我尴尬的解决方案是: 我的意思是它有这样的代码:happy Integer->Integer->IsHappyMemo Bool isHappy_uu1=返回真值 isHappy路径

解决GoogleCodeJam()中的一个问题我提出了一个笨拙的(代码方面的)解决方案,我对如何改进它感兴趣

简而言之,问题描述是:对于给定列表中的所有基,找到大于1的最小数,对于该数,迭代计算的数字平方和达到1

或pseudo Haskell中的描述(如果
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中断循环更不寻常
    您的代码有点太无意义了:

    (`when` mzero) . isJust =<<
       runMaybeT (mapM_ f bases)
    
    现在我们将重点放在函数solve1上,让我们简化它。 一个简单的方法是移除内部的MaybeT单子。当一个快乐的数字被发现时,你可以用另一种方法来代替一个永远的循环,只有当 数字是不快乐的

    而且,你也不需要国家单子,是吗?总是可以用显式参数替换状态

    应用这些想法solve1现在看起来好多了:

    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