微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 SICP 中推广素数对

如何解决在 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 举报,一经查实,本站将立刻删除。