SICP第二章第82页,嵌套映射。
问题0:给定自然数n,找出所有不同的有序对i和j,其中1 <= j < i <= n,使得 i + j 是素数。
我们这里只考虑问题的前一部分,并记为
问题1: 给定自然数n,找出所有不同的有序对i和j,记为( i, j ),其中1 <= j < i <= n。
在问题0中,序对(i,j)和(j,i)是等价的,所以我们在问题1中也延续这个约定。
在命令式编程语言中,我们会用嵌套for循环来解决这个问题,示例代码如下:
for(int i = 1; i < n; i++){
for(int j = 1; j < i; j++){
print(i,j);
}
}
这里,我们要用lisp的思路来解决这个问题。初次用lisp来思考,会很不适应,即使在看到了书中给出的的代码,也没有一个直观的理解。问题在于自己对于嵌套映射并没有一个更加深入的、直观的理解。
经过思考,对这个问题有了一个直观的理解,也具有可操作性。
核心思路:
嵌套映射,就是映射之后再映射,所以,要分成两个步骤来处理。
具体步骤(针对问题1):
第一步:找到第一次映射的目标,问题1中涉及两个变量,i和j,我们先处理i,i的取值范围是(1 2 3 ... n-1),我们另i映射到其自身,形成第一个序列;
第二步:将第一步的结果序列中的每个值(这里记为i)映射到(i,j),其中1 <= j < i。
这里关键是要形成映射的图像,然后我就发现lisp代码是多么的直观。
第一步:
将i取值范围映射到其自身,没什么可解释的。但从问题中发现这个初始序列,是最关键的。
假设n=10。
(def n 10)
(map (fn [i] (identity i))
(range 1 n))
第二步:
根据具体的问题不同,这一步的具体操作也不同。在这个问题中,给定i,需要找出所有的序对(i,j),其中1 <= j < i。
从映射的角度来看,就是将i映射到序对(i,j),其中1 <= j < i。
(def i 5)
(map (fn [j] (list i j))
(range 1 i))
现在,将上面两个步骤组合到一起。
(map (fn [i] (map (fn [j] (list i j))
(range 1 i)))
(range 1 n))