了解concatMap递归

时间:2015-07-28 10:34:06

标签: haskell recursion

我总体上感到困惑,并且正在寻找一个非常详细和解释性的答案,关于这段代码如何运作:

let xs = [1] ++ concatMap (\x -> [x+1,x*10]) xs in xs

concatMap如何知道要映射和连接的内容?

我理解更多基本的例子:

let x = [1] ++ x

此处评估为[1] ++ [1] ++ [1] ..

但我似乎不了解concatMap的第一个例子。这对我来说没有意义。我可以经常使用递归而没有问题。但是,这一段代码非常令人困惑。

3 个答案:

答案 0 :(得分:3)

让我们尝试一个更简单的例子:

let xs = 1 : xs in xs

好的,xs指向(:)节点。此处的头指针指向1,尾指针指向xs(即返回自身)。所以这可以是循环列表,也可以是无限列表。 (Haskell认为这两者是一回事。)到目前为止,这么好。

现在,让我们尝试一个更难的例子:

let xs = 1 : map (+1) xs in xs

你知道这会做什么吗?

因此xs指向(:)节点。头指针指向1.尾指针指向表达式map (+1) xsxs再次指向顶部。

如果您尝试“查看”此列表的内容,将导致map表达式开始执行。 map的定义是

map f js =
  case js of
    k:ks -> (f k) : (map f ks)
    []   -> []

因此map会查看xs以查看它是[]还是(:)。我们知道,它是(:)。所以第一种模式适用。

这意味着整个map (+1) xs表达式被(:)覆盖,其头指针指向(+1) 1,其尾指针指向map (+1) xs2xs2表示指向xs的尾部的指针。

此时,检查(+1) 1会将其变为2。所以现在我们基本上已经

xs = 1 : 2 : map (+1) xs2
           ^           |
           |___________|

当您检查列表时,此循环重复。至关重要的是,map每时每刻都指向一个节点。如果它遇到了自己,你会遇到问题。但是map只查看我们已经计算过的节点,所以没关系。

然后,最终结果为xs = 1 : 2 : 3 : 4 : ...

如果你能理解,你应该能够理解你自己更复杂的例子。

如果您希望让头部受伤,请尝试:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

这是一个标准的Haskell咒语,用于在O(N)时间内吐出Fibonacci数(而不是O(N * N),因为更明显的递归会给你)。

答案 1 :(得分:1)

concatMap 视为 concat map concat.map )的简单组合。

在这种特殊情况下,您使用1初始化xs。一旦开始运行地图,它将提升lambda以在1(列表中的第一个位置)上操作并创建包含两个值2和10的列表Concat只是从该列表中提取这两个值并将它们放在xs中,将它们与现有的1连接起来。此时,xs包含1,2和10( xs = [1,2,10] )。

现在,xs包含1,2和10并且map将重复该过程(当然,从列表中的第二个位置开始),现在在2上运行并创建包含3和20的列表以及包含11的第二个列表在10上操作时为100(列表中的第三个位置)。 Concat现在将提取这4个值并将它们附加到xs的内容中。现在xs包含1,2,10,3,20,11和100( xs = [1,2,10,3,20,11,100] )。

您可以冲洗并重复,此时间映射在列表中的第四个位置(以及每个后续位置)上运行,并且concat执行其工作以删除新的列表容器并将值直接放入顶级列表中。如您所见,此过程将生成无限列表。

这有帮助吗?

答案 2 :(得分:1)

首先,concat是什么?它连接列表中的列表:

concat [ [1], [2],    [3] ] = [ 1, 2, 3 ]
concat [ [1], [2,22], [3] ] = [ 1, 2, 22, 3 ]

等等。 map做了什么?它会转换显示的列表中的每个元素:

map (1+)               [1, 2, 3] = [ 2, 3, 4 ]
map (:[])              [1, 2, 3] = [ [1], [2], [3] ]
map (\x-> [x+1, x*10]) [1, 2, 3] = [ [2,10], [3,20], [4,30] ]

concatMap f xsconcat (map f xs)相同:

concatMap (\x-> [x+1, x*10]) [1, 2, 3] 
 = concat (map (\x-> [x+1, x*10]) [1, 2, 3])
 = concat [ [2,10], [3,20], [4,30] ]
 = [ 2,10, 3,20, 4,30 ]

但是,它不需要看到输入列表到最后,为了继续,逐个生成它的元素。这是因为哈斯克尔的懒惰。简单地说,

   concat [ [2,10], [3,20], [4,30] ]
 = [ 2,10, 3,20, 4,30 ]
 = [ 2,10] ++ concat [ [3,20], [4,30] ]

这意味着实际上,

concat xs == foldr (++) [] xs
-- concat [a,b,...,n] = a ++ (b ++ (... ++ (n++[])...))

concatMap f xs == foldr ((++).f) [] xs
-- concatMap f [a,b,...,n] = f a ++ (f b ++ (... ++ (f n++[])...))

所以它会逐步增加。对于你的例子,

let xs = [1] ++ concatMap (\x -> [x+1,x*10]) xs in xs
== let xs = [1] ++ foldr ((++).(\x -> [x+1,x*10])) [] xs in xs
== let xs = [1] ++ foldr (\x -> ([x+1,x*10] ++)) [] xs in xs
== let xs = [1] ++ foldr (\x r -> x+1 : x*10 : r) [] xs in xs

这只是意味着:xs是一个列表,其中包含1,然后是x+1x*10,用于 {中的每个元素x {1}} - 从头开始。我们也可以把它写成

xs

因此对于1, 2 10 将被"追加" 在列表的结尾,然后是2, 3 20 将被生成,10 - 11 100 ,依此类推:

xs = 1 : [y | x <- xs, y <- [x+1, x*10]]

当然,这不能单独评估;定义是&#34;休眠&#34;直到使用,例如打印xs = 1 a b c d e f g h .... [2,10]=[a,b] = 1 2 10 c d e f g h .... [3,20]=[c,d] = 1 2 10 3 20 e f g h .... [11,100]=[e,f] .... 的前6个元素:

  

前奏&GT;让xs = 1:[y | x <-xs,y < - [x + 1,x * 10]]
  前奏&GT;需要6 xs
  [1,2,10,3,20,11]

正如我们所看到的,这里真正定义的不是无限列表 - 毕竟没有无限的东西 - 而是计算可能需要的元素的过程。

编写此定义的另一种方法是

xs

其中计算结构更清晰:xs = 1 : next xs where next (x:xs) = x+1 : x*10 : next xs &#34;回顾&#34;进入next,因为它被定义,前1个后退;然后2;然后3;等(因为它为它消耗的每个产生两个新的列表元素;因此这个定义高效)。这是&#34; corecursive&#34; 定义的特征。其计算以

进行
xs