所以我到目前为止的情况是这样的:
combs :: [[Char]]
combs = [[i] ++ [j] ++ [k] ++ [l] | i <- x, j <- x, k <- x, l <- x]
where x = "abc"
所以这是n = 4的工作函数,有没有办法使这个工作适用于任意数量的生成器?我可以编程为n = 1,2,3等..但理想情况下需要它适用于任何给定的n。作为参考,x只是一个任意字符串的唯一字符。我正在努力想办法以某种方式将其提取出来用于n个生成器。
答案 0 :(得分:6)
您可以使用replicateM
:
replicateM :: Applicative m => Int -> m a -> m [a]
E.g:
generate :: Num a => Int -> [[a]]
generate = flip replicateM [1,2,3]
生成给定长度的所有可能列表,并由元素1..3
组成。
答案 1 :(得分:5)
据我所知,你不能用任意数量的生成器构造列表推导,但通常如果你做任意深度的事情,递归就是这样做的。
所以我们必须考虑从本身来解决这个问题。如果您想要使用x
中的字符生成所有可能的字符串。在n = 0
的情况下,我们可以生成一个字符串:空字符串。
combs 0 = [""]
所以包含一个元素[]
的列表。
现在我们想要生成一个字符的字符串,我们当然可以简单地返回x
:
combs 1 = x
现在的问题是n > 1
案例该做什么。在这种情况下,我们可以获得长度为n-1
的所有字符串,并且对于每个这样的字符串,以及x
中的每个这样的字符,都会生成一个新字符串。像:
combs n = [ (c:cs) | c <- x, cs <- combs (n-1) ]
请注意,这会使第二种情况(n = 1
)变得多余。我们可以从c
中选择一个字符x
,并将其添加到空字符串中。所以基本的实现是:
combs :: Int -> [[Char]]
combs 0 = [""]
combs n = [(c:cs) | c <- x, cs <- combs (n-1)]
where x = "abc"
现在我们仍然可以寻求改进。列表推导基本上是列表monad的语法糖。所以我们可以在这里使用liftA2
:
import Control.Applicative(liftA2)
combs :: Int -> [[Char]]
combs 0 = [""]
combs n = liftA2 (:) x (combs (n-1))
where x = "abc"
我们可能还想让这组字符成为参数:
import Control.Applicative(liftA2)
combs :: [Char] -> Int -> [[Char]]
combs _ 0 = [""]
combs x n = liftA2 (:) x (combs (n-1))
我们不必将我们限制为字符,我们可以为所有可能的类型生成 certesian power :
import Control.Applicative(liftA2)
combs :: [a] -> Int -> [[a]]
combs _ 0 = [[]]
combs x n = liftA2 (:) x (combs (n-1))
答案 2 :(得分:2)
首先,我将理解翻译为一元表达。
x >>= \i -> x >>= \j -> x >>= \k -> x >>= \l -> return [i,j,k,l]
使用n = 4
,我们发现我们有4个x
,并且通常会有n
x
个。{因此,我正在考虑x
长度为n
的列表。
[x,x,x,x] :: [[a]]
我们如何从[x,x,x,x]
转向monadic表达?第一个好的猜测是foldr
,因为我们想要对列表的每个元素做一些事情。特别是,我们希望从每个x
中获取一个元素,并形成一个包含这些元素的列表。
foldr :: (a -> b -> b) -> b -> [a] -> b
-- Or more accurately for our scenario:
foldr :: ([a] -> [[a]] -> [[a]]) -> [[a]] -> [[a]] -> [[a]]
为foldr提出两个术语,我将其称为f :: [a] -> [[a]] -> [[a]]
和z :: [[a]]
。我们知道foldr f z [x,x,x,x]
是什么:
foldr f z [x,x,x,x] = f x (f x (f x (f x z)))
如果我们在早期的monadic表达式中添加括号,我们就有了这个:
x >>= \i -> (x >>= \j -> (x >>= \k -> (x >>= \l -> return [i,j,k,l])))
您可以看到两个表达式看起来相似。我们应该能够找到f
和z
来使它们相同。如果我们选择f = \x a -> x >>= \x' -> a >>= \a' -> return (x' : a')
,我们会:
f x (f x (f x (f x z)))
= (\x a -> a >>= \a' -> x >>= \x' -> return (x' : a')) x (f x (f x (f x z)))
= f x (f x (f x z)) >>= \a' -> x >>= \x' -> return (x' : a')
= f x (f x (f x z)) >>= \a' -> x >>= \l -> return (l : a')
= (f x (f x z) >>= \a' -> x >>= \k -> return (k : a')) >>= \a' -> x >>= \l -> return (l : a')
= f x (f x z) >>= \a' -> x >>= \k -> x >>= \l -> return (l : k : a')
i,j,k,l
的顺序颠倒到l,k,j,i
,但在查找组合的情况下,这应该是无关紧要的。我们可以试试a' ++ [x']
,如果它真的令人担忧的话。最后一步是因为(a >>= \b -> c) >>= \d -> e
与a >>= \b -> c >>= \d -> e
相同(在考虑变量卫生时),return a >>= \b -> c
与(\b -> c) a
相同。
如果我们继续展开这个表达式,最终我们会在前面找到z >>= \a' -> …
。那么唯一有意义的选择是z = [[]]
。这意味着foldr f z [] = [[]]
可能不合适(更喜欢[]
)。相反,我们可能会使用foldr1
(对于非空列表,我们可能会使用Data.NonEmpty
),或者我们可能会为combs
添加空列表的单独子句。
关注f = \x a -> x >>= \x' -> a >>= \a' -> return (x' : a')
我们可能会发现这种有用的等价:a >>= \b -> return (c b) = c <$> a
。因此,f = \x a -> x >>= \x' -> (x' :) <$> a
。然后,a >>= \b -> c (g b) = g <$> a >>= \b -> c
等f = (:) <$> x >>= \x' -> x' <$> a
。最后,a <*> b = a >>= \x -> x <$> b
等f = (:) <$> x <*> a
。
sequenceA
的{{3}}是foldr (\x a -> (:) <$> x <*> a) (pure [])
,这正是我们在这里提出的。这可以进一步缩短为foldr (liftA2 (:)) (pure [])
,但可能存在一些优化差异,导致实现者不选择此选项。
最后一步是仅提供n
x
的列表。这只是The official implementation replicate n x
。碰巧有一个同时进行复制和排序的功能,称为replicate replicateM n x
。