我有一个列表理解,如下所示:
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?
答案 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 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
]
答案 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
。这样您就不需要反复处理整个素数列表,而是始终只处理您实际需要的素数。