简体   繁体   English

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

[英]Haskell List Comprehensions with Arbitrary number of Generators

So what I have so far is something like this: 所以我到目前为止的情况是这样的:

combs :: [[Char]]
combs = [[i] ++ [j] ++ [k] ++ [l] | i <- x, j <- x, k <- x, l <- x]
  where x = "abc"

So this is the working function for n = 4, is there any way to make this work for an arbitrary number of generators? 所以这是n = 4的工作函数,有没有办法使这个工作适用于任意数量的生成器? I could program in for n = 1, 2, 3 etc.. but ideally need it to work for any given n. 我可以编程为n = 1,2,3等..但理想情况下需要它适用于任何给定的n。 For reference, x is just an arbitrary string of unique characters. 作为参考,x只是一个任意字符串的唯一字符。 I'm struggling to think of a way to somehow extract it to work for n generators. 我正在努力想办法以某种方式提取它以适应n个生成器。

You can use replicateM : 您可以使用replicateM

replicateM :: Applicative m => Int -> m a -> m [a]

Eg: 例如:

generate :: Num a => Int -> [[a]]
generate = flip replicateM [1,2,3]

to generate all possiible lists of a given length and consisting of elements 1..3 . 生成给定长度的所有可能列表,并由元素1..3组成。

As far as I know, you can not construct list comprehension with an arbitrary number of generators, but usually if you do something with arbitrary depth, recursion is the way to do it. 据我所知,你不能用任意数量的生成器构造列表推导,但通常如果你做任意深度的事情, 递归就是这样做的方法。

So we have to think of solving this, in terms of itself. 所以我们必须考虑从本身来解决这个问题。 If you want all possible strings that can be generated with the characters in x . 如果您想要所有可能使用x的字符生成的字符串。 In case n = 0 , we can generate exactly one string: the empty string. n = 0情况下,我们可以生成一个字符串:空字符串。

combs 0 = [""]

so a list with one element [] . 所以包含一个元素[]的列表。

Now in case we want to generate strings with one characters, we can of course simply return x : 现在我们想要生成一个字符的字符串,我们当然可以简单地返回x

combs 1 = x

and now the question is what to do in case n > 1 . 现在的问题是如果n > 1 ,该怎么做。 In that case we can obtain all the strings with length n-1 , and and for each such string, and each such character in x , produce a new string. 在这种情况下,我们可以获得长度为n-1所有字符串,并且对于每个这样的字符串,以及x每个这样的字符,都会产生一个新的字符串。 Like: 喜欢:

combs n = [ (c:cs) | c <- x, cs <- combs (n-1) ]

Note that this makes the second case ( n = 1 ) redundant. 请注意,这使第二种情况( n = 1 )冗余。 We can pick a character c from x , and prepend that to the empty string. 我们可以从x选择一个字符c ,并将其添加到空字符串中。 So a basic implementation is: 所以一个基本的实现是:

combs :: Int -> [[Char]]
combs 0 = [""]
combs n = [(c:cs) | c <- x, cs <- combs (n-1)]
    where x = "abc"

Now we can still look for improvements. 现在我们仍然可以寻求改进。 List comprehensions are basically syntactical sugar for the list monad. 列表推导基本上是列表monad的语法糖。 So we can use liftA2 here: 所以我们可以在这里使用liftA2

import Control.Applicative(liftA2)

combs :: Int -> [[Char]]
combs 0 = [""]
combs n = liftA2 (:) x (combs (n-1))
    where x = "abc"

we probably also want to make the set of characters a parameter: 我们可能还想让这组字符成为一个参数:

import Control.Applicative(liftA2)

combs :: [Char] -> Int -> [[Char]]
combs _ 0 = [""]
combs x n = liftA2 (:) x (combs (n-1))

and we do not have to restrict us to characters, we can produce a certesian power for all possible types: 我们不必将我们限制在角色中,我们可以为所有可能的类型产生一种证书权力

import Control.Applicative(liftA2)

combs :: [a] -> Int -> [[a]]
combs _ 0 = [[]]
combs x n = liftA2 (:) x (combs (n-1))

First I would translate the comprehension as a monadic expression. 首先,我将理解翻译为一元表达。

x >>= \i -> x >>= \j -> x >>= \k -> x >>= \l -> return [i,j,k,l]

With n = 4 we see we have 4 x 's, and generally will have n x 's. n = 4我们看到我们有4个x ,并且通常会有n x Therefore, I am thinking about a list of x 's of length n . 因此,我正在考虑长度为nx列表。

[x,x,x,x] :: [[a]]

How might we go from [x,x,x,x] to the monadic expression? 我们怎样才能从[x,x,x,x]到monadic表达式? A first good guess is foldr , since we want to do something with each element of the list. 第一个好的猜测是foldr ,因为我们想要对列表的每个元素做一些事情。 Particularly, we want to take an element from each x and form a list with these elements. 特别是,我们希望从每个x获取一个元素,并形成包含这些元素的列表。

foldr :: (a -> b -> b) -> b -> [a] -> b
-- Or more accurately for our scenario:
foldr :: ([a] -> [[a]] -> [[a]]) -> [[a]] -> [[a]] -> [[a]]

There are two terms to come up with for foldr, which I will call f :: [a] -> [[a]] -> [[a]] and z :: [[a]] . foldr有两个术语,我将其称为f :: [a] -> [[a]] -> [[a]]z :: [[a]] We know what foldr fz [x,x,x,x] is: 我们知道foldr fz [x,x,x,x]是什么:

foldr f z [x,x,x,x] = f x (f x (f x (f x z)))

If we add parentheses to the earlier monadic expression, we have this: 如果我们将括号添加到早期的monadic表达式中,我们有:

x >>= \i -> (x >>= \j -> (x >>= \k -> (x >>= \l -> return [i,j,k,l])))

You can see how the two expressions are looking similar. 您可以看到两个表达式看起来如何相似。 We should be able to find an f and z to make them the same. 我们应该能够找到fz来使它们相同。 If we choose f = \\xa -> x >>= \\x' -> a >>= \\a' -> return (x' : a') we get: 如果我们选择f = \\xa -> 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')
  • Note that I have reversed the order of i,j,k,l to l,k,j,i but in context of finding combinations, this should be irrelevant. 请注意,我已将i,j,k,l的顺序颠倒为l,k,j,i但在查找组合的上下文中,这应该是无关紧要的。 We could try a' ++ [x'] instead if it was really of concern. 我们可以尝试a' ++ [x']如果它真的值得关注的话。

The last step is because (a >>= \\b -> c) >>= \\d -> e is the same as a >>= \\b -> c >>= \\d -> e (when accounting for variable hygiene) and return a >>= \\b -> c is the same as (\\b -> c) a . 最后一步是因为(a >>= \\b -> c) >>= \\d -> ea >>= \\b -> c >>= \\d -> e (当计算变量时)卫生)并return a >>= \\b -> c(\\b -> c) a

If we keep unfolding this expression, eventually we will reach z >>= \\a' -> … on the front. 如果我们继续展开这个表达式,最终我们将在前面达到z >>= \\a' -> … The only choice that makes sense here then is z = [[]] . 那么唯一有意义的选择是z = [[]] This means that foldr fz [] = [[]] which may not be desirable (preferring [] instead). 这意味着foldr fz [] = [[]]可能并不理想(更喜欢[] )。 Instead, we might use foldr1 (for non-empty lists, and we might use Data.NonEmpty ) or we might add a separate clause for empty lists to combs . 相反,我们可能会使用foldr1 (对于非空列表,我们可能使用Data.NonEmpty ),或者我们可能会为空列表添加一个单独的子句来combs

Looking at f = \\xa -> x >>= \\x' -> a >>= \\a' -> return (x' : a') we might realise this helpful equivalence: a >>= \\b -> return (cb) = c <$> a . f = \\xa -> x >>= \\x' -> a >>= \\a' -> return (x' : a')我们可能会发现这个有用的等价: a >>= \\b -> return (cb) = c <$> a Therefore, f = \\xa -> x >>= \\x' -> (x' :) <$> a . 因此, f = \\xa -> x >>= \\x' -> (x' :) <$> a Then also, a >>= \\b -> c (gb) = g <$> a >>= \\b -> c and so f = (:) <$> x >>= \\x' -> x' <$> a . 然后, a >>= \\b -> c (gb) = g <$> a >>= \\b -> c ,所以f = (:) <$> x >>= \\x' -> x' <$> a Finally, a <*> b = a >>= \\x -> x <$> b and so f = (:) <$> x <*> a . 最后, a <*> b = a >>= \\x -> x <$> b ,所以f = (:) <$> x <*> a

The official implementation of sequenceA for lists is foldr (\\xa -> (:) <$> x <*> a) (pure []) , exactly what we came up with here too. 列表的sequenceA 的官方实现foldr (\\xa -> (:) <$> x <*> a) (pure []) ,这正是我们在这里提出的。 This can be further shortened as foldr (liftA2 (:)) (pure []) but there is possibly some optimisation difference that made the implementors not choose this. 这可以进一步缩短为foldr (liftA2 (:)) (pure [])但是可能存在一些优化差异,这使得实现者不会选择它。

Last step is to merely come up with a list of n x 's. 最后一步是仅列出n x的列表。 This is just replicate replicate nx . 这只是复制 replicate nx There happens to be a function which does both replication and sequencing, called replicateM replicateM nx . 碰巧有一个同时进行复制和排序的函数,称为replicateM replicateM nx

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

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