我总体上感到困惑,并且正在寻找一个非常详细和解释性的答案,关于这段代码如何运作:
let xs = [1] ++ concatMap (\x -> [x+1,x*10]) xs in xs
concatMap如何知道要映射和连接的内容?
我理解更多基本的例子:
let x = [1] ++ x
此处评估为[1] ++ [1] ++ [1] ..
但我似乎不了解concatMap的第一个例子。这对我来说没有意义。我可以经常使用递归而没有问题。但是,这一段代码非常令人困惑。
答案 0 :(得分:3)
让我们尝试一个更简单的例子:
let xs = 1 : xs in xs
好的,xs
指向(:)
节点。此处的头指针指向1
,尾指针指向xs
(即返回自身)。所以这可以是循环列表,也可以是无限列表。 (Haskell认为这两者是一回事。)到目前为止,这么好。
现在,让我们尝试一个更难的例子:
let xs = 1 : map (+1) xs in xs
你知道这会做什么吗?
因此xs
指向(:)
节点。头指针指向1.尾指针指向表达式map (+1) xs
,xs
再次指向顶部。
如果您尝试“查看”此列表的内容,将导致map
表达式开始执行。 map
的定义是
map f js =
case js of
k:ks -> (f k) : (map f ks)
[] -> []
因此map
会查看xs
以查看它是[]
还是(:)
。我们知道,它是(:)
。所以第一种模式适用。
这意味着整个map (+1) xs
表达式被(:)
覆盖,其头指针指向(+1) 1
,其尾指针指向map (+1) xs2
( xs2
表示指向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 xs
与concat (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+1
和x*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