为什么在这次迭代中会在这里产生空列表?

问题描述

让我们使用以下函数获取一对数字:

; (range 1 3) --> '(1 2 3)
(define (range a b)
  (if (> a b) nil
     (cons a (range (+ 1 a) b))))

; generate pair of two numbers with  1 <=  i < j <= N
(define (get-pairs n)
  (map (lambda (i)
         (map (lambda (j) (list i j))
              (range 1 (- i 1))))
       (range 1 n)))
(get-pairs 2)
; (() ((2 1)))

(get-pairs 3)
(() ((2 1)) ((3 1) (3 2)))

为什么上面产生 '() 作为输出的第一个元素?将此与 python 进行比较,我希望它只给出三对,例如:

>>> for i in range(1,3+1): # +1 because the range is n-1 in python
...     for j in range(1,i-1+1):
...         print (i,j)
...
(2,1)
(3,2)

我想这可能与 i 何时为 1 有关?

(map (lambda (j) (list 1 j)) '())
; ()

这只是 Scheme 中的一个标识,即带有空列表的映射始终是一个空列表吗?

解决方法

i 为 1 时,内部映射超过 (range 1 0),根据您自己的定义,它是 ()。由于 map 接受一个过程和一个值列表(或多个列表),依次将过程应用于列表中的每个值,并返回一个包含结果的列表,将任何过程映射到包含 no values 将返回一个不包含任何值的列表。

map 创建一个简单的定义以了解其工作原理可能会有所帮助。请注意,此定义并非全功能;它只需要一个列表参数:

(define (my-map proc xs)
  (if (null? xs)
      '()
      (cons (proc (car xs))
            (my-map proc (cdr xs)))))

这里,当输入列表为空时,没有要映射的值,因此返回一个空列表。否则,过程 proc 将应用于输入列表中的第一个值,并且结果被用于映射到列表其余部分的结果。

一些观察:

首先,在标准 Scheme 或 vanilla Racket 中,空列表不由 nil 表示,您不应该使用它。在 Scheme nil 的早期,它被允许作为来自其他 lisps 的程序员的拐杖,但这种情况已经很长时间了。我不认为它曾经出现在任何 RnRS 标准中,但 nil 可能在某些特定实现中幸存下来,直到 R4RS(1991)。 SICP来自那个时代。今天,您应该使用 '() 来表示 Scheme 中的空列表文字,以便您的代码可以在任何 Scheme 实现上运行。 Racket 的 #lang sicp 允许直接运行书中的代码,但这不应阻止您使用通用符号。请注意,Common Lisp 确实使用 nil 作为自我评估符号来表示空列表和布尔值 false。在 Scheme 中看到这一点今天看起来不太对。

其次,当你试图理解 Scheme 代码时,你可能会更多地误入歧途,而不是通过思考 Python 的智慧。在这种特殊情况下,map 一个迭代构造,但它与 for 循环不同。 for 循环通常用于产生副作用,但 map 用于转换列表。 Scheme 有一个 for-each 形式,意在用于它的副作用,从这个意义上说更像是一个 for 循环。不过,上面发布的 Python 版本与 Scheme 版本完全不同。结果被打印出来,而不是在列表中返回结果。 Scheme代码中,当i为1时,内部映射结束(range 1 0) --> ()。但是,在 Python 代码中,当 i 为 1 时,内循环结束 range(1,1),因此此 for 循环的主体不会被执行,也不会打印任何内容。

最好仔细考虑您想要理解的 Scheme 代码,回到基本定义,而不是将基于 Python 的模型拼凑起来,而这些模型可能没有考虑过极端情况。