具有任意数量的生成器的Haskell列表理解

时间:2017-11-11 12:04:26

标签: list haskell

所以我到目前为止的情况是这样的:

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个生成器。

3 个答案:

答案 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])))

您可以看到两个表达式看起来相似。我们应该能够找到fz来使它们相同。如果我们选择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 -> ea >>= \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 -> cf = (:) <$> x >>= \x' -> x' <$> a。最后,a <*> b = a >>= \x -> x <$> bf = (:) <$> 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