为什么将此递归映射函数仅应用于列表中的最后两个元素?

时间:2019-06-09 06:58:48

标签: list haskell lazy-evaluation map-function lazy-sequences

这是给出的问题:下表中的前8个元素是什么?

mystery = 0 : 10 : (map(+1)mystery)

答案是[0,10,1,11,2,12,3,13...]

但是我认为答案应该是[0,10,1,11,1,11,2,12]。以下步骤说明了原因:

1)我们得到了;列表[0,10],因此在第一次应用该函数后,我们有了列表[0,10,1, 11] 2)现在我们有了列表[0,10,1,11],因此再次应用该函数后,结果列表应为[0,10,1,11,1,11,2,12]

显然不是这样。谁能解释为什么?

3 个答案:

答案 0 :(得分:6)

在深入探讨mystery的定义之前,让我们看一下map遵守的定律之一:

map f (map g xs) == map (f . g) xs

此法的非正式证明很容易遵循:

map f (map g [x1, x2, ..., xn]) == map f [g x1, g x2, ..., g xn]
                                == [f (g x1), f (g x2), ..., f (g xn)]
                                == [(f.g) x1, (f.g) x2, ..., (f.g) xn]
                                == map (f.g) [x1, x2, ..., xn]

考虑到这一点,让我们逐步扩展mystery

mystery == 0 : 10 : map (+1) mystery
        -- by definition of mystery
        == 0 : 10 : map (+1) (0 : 10 : map (+1) mystery)
        -- by definition of map and the fact that  0 + 1 == 1
        == 0 : 10 : 1 : map (+1) (10 : map (+1) mystery)
        -- by definition of map and the fact that 10 + 1 == 11
        == 0 : 10 : 1 : 11 : map (+1) (map (+1) mystery)
        -- using the law above, and the fact that (+1) . (+1) == (+2)
        == 0 : 10 : 1 : 11 : map (+2) mystery
        == 0 : 10 : 1 : 11 : map (+2) (0 : 10 : map (+1) mystery)
        == 0 : 10 : 1 : 11 : 2 : map (+2) (10 : map (+1) mystery)
        == 0 : 10 : 1 : 11 : 2 : 12 : map (+2) (map (+1) mystery)
        == 0 : 10 : 1 : 11 : 2 : 12 : map (+3) mystery
        -- etc

您不是从有限列表[0, 10]开始;您可以从一个无限列表开始,该列表以0和10开始 ,其余元素以递归方式定义。

从某种意义上说,列表没有封闭的形式,但这没关系;懒惰意味着您仅在需要时将map应用于mystery才能获得请求的项目。例如,head mysteryhead (tail mystery)都不需要评估对map的呼叫,并且head (tail (tail mystery))仅需要将(+1)映射到head mystery ,而不是整个无限列表。

懒惰模糊了列表和计算列表的算法之间的区别。

答案 1 :(得分:4)

让我们使用map的递归定义来完成它:

map _ [] = []
map f (x:xs) = f x : map f xs

自从我们有了

mystery = 0:10:(map (+1) mystery)

我们已经知道

mystery = [0, 10, ...]

,而...代表map (+1) mystery。因此,让我们使用上面的定义进行计算。

我们将其应用于的列表显然不是空的-它以0和10开头。因此,我们使用第二行,其中x为0,xs为{{1} }:

10:(map (+1) mystery)

或者,再次将公式用于第一层嵌套:

map (+1) mystery = 1:(map (+1) (10:(map (+1) mystery)))

因此,回到map (+1) mystery = 1:11:(map (+1) (map (+1) mystery)) 本身,我们现在知道它的前四个元素:

mystery

mystery = [0, 10, 1, 11, ...] 代表...的内容。也就是说,基于以上结果:

map (+1) (map (+1) mystery)

在这里,我将为您保留评估的详细信息,因为现在应该清楚会发生什么情况:前两个元素(在map (+1) (1:11:(map (+1) (map (+1) mystery))) 中将是第5个和第6个元素)将是2和12。其余为mystery。通过完全相同的过程,它们将从3和13开始。如此一来,就尽您所能进行计算了。

答案 2 :(得分:1)

因为

mystery = 0 : 10 : map (+1) mystery

根据(!!)(:)map的定义,情况就是这样

mystery !! 0 = 0          -- pseudocode
mystery !! 1 = 10
mystery !! n | n > 1
             = (0 : 10 : map (+1) mystery) !! n
             = (10 : map (+1) mystery) !! (n-1)
             = (map (1+) mystery) !! (n-2)
             = 1 + (mystery !! (n-2))

有您的答案。

说明:

--         0   1  2   3  4   5  6        -- n
mystery = [0, 10, 1, 11, 2, 12, 3, ...
--               /   /  /   /  /
--               0   1  2   3  4         -- n-2
--              [0, 10, 1, 11, 2, ...

因此,所有元素都是相对于前一个,两个位置优先的元素定义的。

另一种记下同一件事的方法是使配对变得清晰(没有 zipping

mystery = 0 : 10 : zipWith (+) mystery
                              (repeat 1)

翻译成命令式伪代码的程序

main = print mystery

实际上与

相同
main :
    a = 0
    b = 10
    print "[" 
    while( True ) :
       print a , 
       print b ,
       a += 1
       b += 1

解决这些问题的原则方法是命名您的所有临时实体。然后,神秘感消失了:

mystery = 0  : 10 : map (+1) mystery
        = x0 : t1 
  where 
  x0 = 0
  t1 = 10 : map (+1) mystery
     = x1 : t2
    where
    x1 = 10
    t2 = map (+1) mystery
       = map (+1) (x0 : t1)
       = x0+1 : map (1+) t1
       = x2 : t3
      where
      x2 = x0+1 = 0+1 = 1
      t3 = map (1+) t1 = map (1+) (x1 : t2)
         = x1+1 : map (1+) t2
         = x3 : t4
        where
        ....

在简化过程中,除了(1+)函数之外,我们什么都没有得到,也没有一个以上。

对于xn := 1 + xn-2以上的所有n,我们得到的都是1