Haskell列表monad循环

时间:2016-01-25 19:15:47

标签: haskell list-comprehension monads

我有一个列表理解,如下所示:

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?

5 个答案:

答案 0 :(得分:7)

以下是我的做法

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个元素的所有组合。 然后我只筛选出升序排列的组合,最后计算剩余列表的产品。

答案 1 :(得分:4)

密切的翻译可能是:

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 ]

因为后者在p3<-xs时会避免扫描p1 <= p2的整个列表,所以无论如何我们都不会生成任何内容。

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

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中删除。

答案 2 :(得分:4)

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

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}equivalent to simply psforM [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
                         ]

答案 3 :(得分:3)

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

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

答案 4 :(得分:1)

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

另一种这样的算法是

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。这样您就不需要反复处理整个素数列表,而是始终只处理您实际需要的素数。