繁体   English   中英

Haskell列表monad循环

[英]Haskell list monad looping

我有一个列表理解,看起来像这样:

cross ps = [  p* pp * ppp | p <- ps, pp <- ps, ppp <- ps, p >= pp , pp >= ppp ]

如何使用monads实现这一点,而无需输入列表名称?

dim ps n = do
    p <- ps
    pp <- ps
    ppp <- ps
    p...p <- ps

    guard (p >= pp && pp >= ppp ... && p...p >=p....p)
    return (p*pp*ppp*p...p)

如何在不显式赋值的情况下使用列表monad?

这是我怎么做的

ascending :: Ord a => [a] -> Bool
ascending list = and $ zipWith (>=) (tail list) list

dim ps n = map product $ filter ascending allComb 
    where allComb = replicateM n ps

replicateM来自Control.Monad ,对于列表monad,它生成给定列表的n元素的所有组合。 然后我只筛选出升序排列的组合,最后计算剩余列表的产品。

密切的翻译可能是:

dim :: Num a => [a] -> Int -> [a]
dim ps n = do
  chosen <- replicateM n ps
  guard $ increasing chosen
  return $ product chosen

increasing :: Ord a => [a] -> Bool
increasing []        = True
increasing xs@(_:ys) = and $ zipWith (<=) xs ys

但是,这可以通过提前放置警卫来改善。 我的意思是:

[ ... | p1<-xs, p2<-xs, p3<-xs, p1 <= p2, p2 <= p3 ]

比...差

[ ... | p1<-xs, p2<-xs, p1 <= p2, p3<-xs, p2 <= p3 ]

因为后者将避免在p1 <= p2时扫描整个列表中的p3<-xs ,所以我们不会生成任何东西。

那么,让我们再试一次,采用更原始的方法:

dim :: Num a => [a] -> Int -> [a]
dim ps 0 = [1]
dim ps n = do
   x  <- ps
   xs <- dim (filter (>=x) ps) (n-1)
   return (x * xs)

现在我们尽早丢弃不可能的替代方案,在递归调用之前将其从ps删除。

也许最容易理解的解决方案是“逐字地使用循环”:

dim ps n = do
    pz <- forM [1..n] $ \_i -> do
       p <- ps
       return p

    guard $ descending pz
    return $ product pz

但是do {p <- ps; return p} do {p <- ps; return p} 相当于简单的ps ,对于forM [1..n] $ \\_i -> ps我们有速记replicateM n ps 所以你得到了chi建议的解决方案。 不过,我会说Luka Horvat实际上好一点。

但是,正如Chi所说的那样,你可以通过不选择所有可能的组合并抛弃绝大多数组合来提高效率,而只是首先选择下降的可能性。 为此,我手动编写一个递归函数:

descendingChoices :: Ord a => Int -> [a] -> [[a]]
descendingChoices 1 ps = [[p] | p<-ps]  -- aka `pure<$>ps`
descendingChoices n ps = [ p : qs | qs <- descendingChoices (n-1) ps
                                  , p <- ps
                                  , all (<=p) qs
                         ]

鉴于您的素数列表按升序排列,您可以完全避免使用警卫,只需生成一组产品即可:

cross :: Int -> [a] -> [[a]]
cross 0 _ = [[]]
cross n [] = []
cross n all@(x:xs) = ((x:) <$> cross (n - 1) all) ++ cross n xs

dim :: Num a => Int -> [a] -> [a]
dim n xs = map product $ cross n xs

如果素数列表不是按升序排列,那么最好的选择是对其进行排序并使用假定列表已排序的算法。 amalloy给了一个,但是你可以通过使用共享( 一个例子 )来生成通过重复(aka cross )生成k组合的功能更加高效。

另一种这样的算法是

dim :: (Num a, Ord a) => Int -> [a] -> [a]
dim 0 xs = [1]
dim n xs = [y * x | x <- xs, y <- dim (n - 1) (takeWhile (<= x) xs)]

请注意takeWhile而不是filter 这样您就不需要一遍又一遍地处理整个素数列表,而是始终只处理您实际需要的素数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM