如何解决在 SICP 中推广素数对
我花了一些时间来研究 SICP 第 2.2.3 节 - 作为常规接口的序列 中“素数对”的生成,例如:
-
(1 3)
不,因为 sum = 4 -
(1 4)
是的,因为 sum = 5(质数)
这是我从头开始的,有效:
#lang sicp
; RANGE helper function
(define (range a b) ; a to b
(if (> a b) nil
(cons a (range (+ 1 a) b))
))
; FILTER helper function
(define (filter func sequence)
(cond ((null? sequence) nil)
((func (car sequence)) (cons (car sequence) (filter func (cdr sequence))))
(else (filter func (cdr sequence)))))
; PRIME helper function
(define (prime? n)
(cond ((or (= n 1) (= n 2)) #t)
((=(modulo n 2) 0) #f)
(else ; see if no modulos=0 under length of sqrt(N)
(= 0
(length (filter (lambda (i) (eq? i #t))
(map (lambda (d) (= (modulo n d) 0)) (range 3 (sqrt n)))))))))
; MAP helper
(define (flatmap func seq)
(if (null? seq) nil (append (func (car seq)) (flatmap func (cdr seq)))))
和实际功能:
; generate pair of two numbers with 1 <= i < j <= N
(define (get-pairs n)
(flatmap (lambda (i)
(map (lambda (j)
(list i j))
(range 1 (- i 1))))
(range 1 n)))
(define (get-prime-pairs n)
(filter (lambda (p) (prime? (apply + p)))
(get-pairs n)))
(get-prime-pairs 4)
; ((2 1) (3 2) (4 1) (4 3))
很整洁。现在我正在尝试编写相同的函数,而不是只执行对,而是执行 size
元组。到目前为止,这是我所拥有的:
(define (get-n-tuples size n)
(define (get-n-tuples-internal i args)
(cond ((= i size) (map (lambda (i) (cons i args)) (range 1 n)))
(else
(flatmap (lambda (k)
(get-n-tuples-internal (+ i 1) (cons k args)))
(range 1 n)))))
(get-n-tuples-internal 1 '()))
(define (get-prime-seqs size num)
(filter (lambda (p) (prime? (apply + p)))
(get-n-tuples size num)))
(get-prime-seqs 4 4)
; ...
; (3 2 4 4)
; (2 3 4 4)
; (1 4 4 4))
我发现困难的一件事是通过执行诸如 (range i (- (min args) 1))
之类的操作来删除函数本身中的重复项。因此,我只是对所有循环使用了相同的 (range 1 n)
。
我将如何正确转换它以模拟初始函数,以便列表中的每个连续数字必须更小,即,如果序列中有三个数字,则 1 <= num1 < num2 < num3 <= N
和一个有效对将是 (4 2 1)
但不是 (1 2 4)
或 (5 1 1)
?
解决方法
您的 2D 案例相当于两个嵌套循环:
for i in 1 to n
for j in 1 to (i-1)
yield (i,j)
flatmap
只是实现机制,为此。是的,这就是“monads”的本质:第二个循环的“形状”(范围)取决于值({{ 1}}) 由第一个产生;此外,从最里面的循环产生的所有值都以一个序列出现,无论循环的深度如何(这里是 2)。
这也是回溯的本质,因为当我们处理完给定i
的所有j
后,控制权返回 进入外循环,然后依次尝试 next i
。
回到我们的业务。自然,3D 案例涉及三个嵌套循环:
i
一般来说,您希望将其推广到 m 个嵌套循环,m = 2,3,4,... .
构建 m 个嵌套循环的标准方法是使用递归。如果我们要使用 for i in 1 to n
for j in 1 to (i-1)
for k in 1 to (j-1)
yield (i,j,k)
,那么我们只需要意识到外循环内的所有结构都代表 m-1 嵌套循环计算:
flatmap
似乎有效:
(define (tuplesND_ ND max_idx)
(define (g m imax)
(if (= m 0)
(list '()) ; at the deepest level
(flatmap (lambda (i)
(map (lambda (tup) (cons i tup)) ; back from
(g (- m 1) (- i 1)))) ; recursion
(range 1 imax))))
(g ND max_idx))
当然,> (display (tuplesND_ 2 3))
((2 1) (3 1) (3 2))
> (display (tuplesND_ 2 4))
((2 1) (3 1) (3 2) (4 1) (4 2) (4 3))
> (display (tuplesND_ 3 3))
((3 2 1))
> (display (tuplesND_ 3 4))
((3 2 1) (4 2 1) (4 3 1) (4 3 2))
> (display (tuplesND_ 4 5))
((4 3 2 1) (5 3 2 1) (5 4 2 1) (5 4 3 1) (5 4 3 2))
在性能方面很糟糕(除非它可用作低级原语),所有这些 flatmap
的所有常量结构复制和重新分配。
当然只有在可变性挑战的语言中才需要它。另一方面,Scheme 拥有其中最大的原语:append
。它能够以自上而下的方式一次性构建列表,无需复制,无需重新分配(这也是假设的内置 set-cdr!
的运作方式)。突变没有任何问题,否则无法观察到!
通过构建元组进入递归,传递一个回调从最深层调用,我们让它做我们需要的是:只需在构造每个元组时打印它,append!
将其打印到不断增长的列表的右端(将其保持为隐藏状态以提高效率,因此可以在 O(1) 中访问它em> 时间),或者我们选择的任何其他内容。
所以,不用再说了,
flatmap
这会在进入的过程中构建元组,进入递归的最深层次,而不是像以前的版本那样在出路时构建它。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。