如何在计划中找到友好对?

时间:2014-05-17 09:33:39

标签: scheme

我是计划新手。 如何找到“友善的朋友”?

(define (SumCD n)
  (define s 1 )
  (set! m (quotient n 2))
  (while (<= i m)
         (if (=(modulo n i) 0)
             (set! s (+ s i)))
         (set! i (+ i 1)) 
  )
 )

在主程序中我想检查(如果(m = SumCD n)和(n = SumCD m))则m和n是友好对。 我怎么能这样做?

3 个答案:

答案 0 :(得分:4)

过度使用set!表示一种强制性的编程风格,在Scheme中通常不鼓励。以下是sum-of-divisors的特定于Racket的实现,根本不使用set!

(define (sum-of-divisors n)
  (define-values (q r) (integer-sqrt/remainder n))
  (for/fold ((sum (if (and (zero? r) (> q 1)) (add1 q) 1)))
            ((i (in-range 2 q))
             #:when (zero? (modulo n i)))
    (+ sum i (quotient n i))))

标准R6RS / R7RS计划中的等效版本,如果您不使用Racket:

(define (sum-of-divisors n)
  (define-values (q r) (exact-integer-sqrt n))
  (let loop ((sum (if (and (zero? r) (> q 1)) (+ q 1) 1))
             (i 2))
    (cond ((>= i q) sum)
          ((zero? (modulo n i))
           (loop (+ sum i (quotient n i)) (+ i 1)))
          (else (loop sum (+ i 1))))))

请注意,这与 相当于您拥有的基于set!的版本。这段代码实际上做的是创建一个内部函数loop,每次都使用新参数进行尾调用。


现在,我们可以相应地定义amicable?perfect?

(define (amicable? n)
  (define sum (sum-of-divisors n))
  (and (not (= n sum))
       (= n (sum-of-divisors sum))))

(define (perfect? n)
  (= n (sum-of-divisors n)))

如果你真的想测试两个号码以确定它们是否是友好对,你可以这样做:

(define (amicable-pair? a b)
  (and (not (= a b))
       (= a (sum-of-divisors b))
       (= b (sum-of-divisors a))))

更新OP的新问题,了解如何使用此功能查找mn之间的友好对。首先,让我们定义一个amicable?的变体,它返回一个数字的友好“同伴”:

(define (amicable-peer n)
  (define sum (sum-of-divisors n))
  (and (not (= n sum))
       (= n (sum-of-divisors sum))
       sum))

如果您使用的是Racket,请使用:

(define (amicable-pairs-between m n)
  (for*/list ((i (in-range m (add1 n)))
              (peer (in-value (amicable-peer i)))
              #:when (and peer (<= m peer n) (< i peer)))
    (cons i peer)))

如果您不使用Racket,请使用:

(define (amicable-pairs-between m n)
  (let loop ((result '())
             (i n))
    (if (< i m)
        result
        (let ((peer (amicable-peer i)))
          (if (and peer (<= m peer n) (< i peer))
              (loop (cons (cons i peer) result) (- i 1))
              (loop result (- i 1)))))))

这样做的方法是,因为列表是从右到左构建的,所以我决定将向下n计算到m,保持只有具有友好对等体的数字,以及对等体在范围内的数字。 (< i peer)检查是为了确保友好对只在结果中出现一次。

示例:

> (amicable-pairs-between 0 10000)
((220 . 284) (1184 . 1210) (2620 . 2924) (5020 . 5564) (6232 . 6368))

更多OP更新(其中他询问递归版本和累积版本之间的区别是什么)。我上面写的amicable-pairs-between版本是累积的。递归版本看起来像这样:

(define (amicable-pairs-between m n)
  (let recur ((i m))
    (if (> i n)
        '()
        (let ((peer (amicable-peer i)))
          (if (and peer (<= m peer n) (< i peer))
              (cons (cons i peer) (recur (+ i 1)))
              (recur (+ i 1)))))))

请注意,这次没有result累加器。但是,它不再是尾递归了。

答案 1 :(得分:1)

您的程序不起作用: i 从未初始化。这是非常糟糕的风格;正确的Scheme程序很少使用whileset!。让我们回到开头。

一个完美的数字等于其适当的除数之和;例如,28的除数是1,2,4,7和14,以及1 + 2 + 4 + 7 + 14 = 28,所以28是一个完整的数。如果 m 的除数之和等于 n 且总和,则两个数字 m n 形成友好对 n 的除数等于 m ;例如,220具有除数1,2,4,5,10,11,20,22,44,55,110,它们总和为284,2844具有除数1,2,4,71,142,其总和为220,所以220和284形成友好对。

计算数字 n 的除数的一种简单方法是尝试从1到⌊ n /2⌋的每个整数,看看它是否除以 n < / EM>:

(define (divisors n)
  (let loop ((i 1) (ds (list)))
    (cond ((< n (+ i i)) (reverse ds))
          ((zero? (modulo n i))
            (loop (+ i 1) (cons i ds)))
          (else (loop (+ i 1) ds)))))

> (divisors 220)
(1 2 4 5 10 11 20 22 44 55 110)
> (divisors 284)
(1 2 4 71 142)
> (divisors 36)
(1 2 3 4 6 9 12 18)

请注意,我们从 n 的除数列表中排除 n ;这是计算友好对时我们想要的,但在某些情况下,您可能希望将 n 添加到 n 的除数列表中。我们可以计算它们的总和,而不是列出除数:

(define (sum-div n)
  (let loop ((i 1) (s 0))
    (cond ((< n (+ i i)) s)
          ((zero? (modulo n i))
            (loop (+ i 1) (+ s i)))
          (else (loop (+ i 1) s)))))

> (sum-div 220)
284
> (sum-div 284)
220
> (sum-div 36)
55

而不是计算到⌊ n /2⌋,更快地注意到除数成对出现,因此只需要计算 n <的平方根/ em>的;当 n 是一个完美的正方形时要小心,以便在总和中恰好包含一个平方根的实例:

(define (divisors n)
  (let loop ((i 2) (ds (list 1)))
    (cond ((<= n (* i i))
            (sort < (if (= n (* i i)) (cons i ds) ds)))
          ((zero? (modulo n i))
            (loop (+ i 1) (cons i (cons (/ n i) ds))))
          (else (loop (+ i 1) ds)))))

(define (sum-div n)
  (let loop ((i 2) (s 1))
    (cond ((<= n (* i i))
            (if (= n (* i i)) (+ i s) s))
          ((zero? (modulo n i))
            (loop (+ i 1) (+ s i (/ n i))))
          (else (loop (+ i 1) s)))))

> (divisors 220)
(1 2 4 5 10 11 20 22 44 55 110)
> (divisors 284)
(1 2 4 71 142)
> (divisors 36)
(1 2 3 4 6 9 12 18)
> (sum-div 220)
284
> (sum-div 284)
220
> (sum-div 36)
55

如果你知道 n 的素数分解,很容易找到 n 的除数:只需要取得因子的成员的乘积。 n ,消除重复。

(define (but-last xs)
  (if (null? xs) (error 'but-last "empty list")
    (reverse (cdr (reverse xs)))))

(define (unique eql? xs)
  (cond ((null? xs) '())
        ((null? (cdr xs)) xs)
        ((eql? (car xs) (cadr xs)) (unique eql? (cdr xs)))
        (else (cons (car xs) (unique eql? (cdr xs))))))

(define (power-set xs)
  (if (null? xs) (list (list))
    (let ((rest (power-set (cdr xs))))
      (append (map (lambda (x) (cons (car xs) x)) rest) rest))))

(define (divisors n)
  (but-last (unique = (sort <
    (map (lambda (xs) (apply * xs))
      (power-set (factors n)))))))

> (divisors 220)
(1 2 4 5 10 11 20 22 44 55 110)
> (divisors 284)
(1 2 4 71 142)
> (divisors 36)
(1 2 3 4 6 9 12 18)

如果通过检查 n的因子的多重性来了解 n 的素数因子分解,则更容易找到 n 的除数之和

(define (sum-div n)
  (define (div f x) (/ (- (expt f (+ x 1)) 1) (- f 1)))
  (let ((fs (factors n)))
    (let loop ((f (car fs)) (fs (cdr fs)) (x 1) (s 1))
      (cond ((null? fs) (- (* s (div f x)) n))
            ((= (car fs) f) (loop f (cdr fs) (+ x 1) s))
            (else (loop (car fs) (cdr fs) 1 (* s (div f x))))))))

> (sum-div 220)
284
> (sum-div 284)
220
> (sum-div 36)
55

查找数字 n 的因子的简单方法是使用素轮;如果 n 是一个大素数或半素数,但这是合理的,否则是合理的:

(define (factors n)
  (define (last-pair xs) (if (null? (cdr xs)) xs (last-pair (cdr xs))))
  (define (cycle . xs) (set-cdr! (last-pair xs) xs) xs)
  (let ((wheel (cons 1 (cons 2 (cons 2 (cycle 4 2 4 2 4 6 2 6))))))
    (let loop ((n (abs n)) (f 2) (wheel wheel) (fs (list)))
      (cond ((< n (* f f)) (if (= n 1) fs (reverse (cons n fs))))
            ((zero? (modulo n f)) (loop (/ n f) f wheel (cons f fs)))
            (else (loop n (+ f (car wheel)) (cdr wheel) fs))))))

鉴于这一切,很容易确定数字 n 是否完美,或者它是否是友好对的一部分:

(define (perfect? n)
  (= n (sum-div n)))

(define (amicable? n)
  (let ((s (sum-div n)))
    (and (< 1 s) (= (sum-div s) n))))

> (perfect? 6)
#t
> (perfect? 28)
#t
> (amicable? 220)
#t
> (amicable? 284)
#t

很容易找到完美的数字和友好的对,但不是一些限制:

(define (perfect limit)
  (let loop ((n 2) (ps (list)))
    (cond ((< limit n) (reverse ps))
          ((= n (sum-div n))
            (loop (+ n 1) (cons n ps)))
          (else (loop (+ n 1) ps)))))

(define (amicable limit)
  (let loop ((n 2) (as (list)))
    (if (< limit n) (reverse as)
      (let ((s (sum-div n)))
        (if (and (< n s) (= n (sum-div s)))
            (loop (+ n 1) (cons (list n s) as))
            (loop (+ n 1) as))))))

> (perfect 10000)
(6 28 496 8128)
> (amicable 10000)
((220 284) (1184 1210) (2620 2924) (5020 5564) (6232 6368))

不是将每个数字分解到一个限制,而是通过筛选找到所有数字的除数之和达到极限要快得多:从1到极限的矢量,每个项目初始化为1.然后,对于每个 i 从2到极限,将 i 添加到 i 的每个倍数:

(define (make-sum-divs n)
  (let ((s (make-vector (+ n 1) 0)))
    (do ((i 1 (+ i 1))) ((< n i) s)
      (do ((j (+ i i) (+ j i))) ((< n j))
        (vector-set! s j (+ i (vector-ref s j)))))))

(define max-sum-div 1000)
(define sum-divs (make-sum-divs max-sum-div))

鉴于筛选,很容易找到完美的数字和友好的对:

(define (perfect limit)
  (when (< max-sum-div limit)
    (set! max-sum-div limit)
    (set! sum-divs (make-sum-divs max-sum-div)))
  (let loop ((n 2) (ps (list)))
    (cond ((< limit n) (reverse ps))
          ((= n (vector-ref sum-divs n))
            (loop (+ n 1) (cons n ps)))
          (else (loop (+ n 1) ps)))))

(define (pairs limit)
  (when (< max-sum-div limit)
    (set! max-sum-div limit)
    (set! sum-divs (make-sum-divs max-sum-div)))
  (let loop ((n 2) (as (list)))
    (if (< limit n) (reverse as)
      (let ((s (vector-ref sum-divs n)))
        (if (and (< s max-sum-div) (< n s)
                 (= n (vector-ref sum-divs s)))
            (loop (+ n 1) (cons (list n s) as))
            (loop (+ n 1) as))))))

> (perfect 1000000)
(6 28 496 8128)
> (pairs 1000000)
((220 284) (1184 1210) (2620 2924) (5020 5564) (6232 6368)
 (10744 10856) (12285 14595) (17296 18416) (63020 76084)
 (66928 66992) (67095 71145) (69615 87633) (79750 88730)
 (100485 124155) (122265 139815) (122368 123152)
 (141664 153176) (142310 168730) (171856 176336)
 (176272 180848) (185368 203432) (196724 202444)
 (280540 365084) (308620 389924) (319550 430402)
 (356408 399592) (437456 455344) (469028 486178)
 (503056 514736) (522405 525915) (600392 669688)
 (609928 686072) (624184 691256) (635624 712216)
 (643336 652664) (667964 783556) (726104 796696)
 (802725 863835) (879712 901424) (898216 980984))

筛分方法比其他两种方法快得多。在我的计算机上,使用试验分区计算友善对不到一百万来计算除数需要12秒,并且分解方法的时间大约相同,但只有大约一秒半的筛子除数总和达到一百万而另外半秒找到友方对,总共两秒钟。

除了友好对之外,还存在友好链,这些链在两个以上的项目之后循环回到开始。例如,数字12496,14288,15472,14536和14264形成长度为5的友好链,因为sum-div(12496)= 14288,sum-div(14288)= 15472,sum-div(15472)= 14536 ,sum-div(14536)= 14264,sum-div(14264)= 12496.找到友好链的程序是找到友好对的程序的变体:

(define (chain n limit)
  (when (< max-sum-div limit)
    (set! max-sum-div limit)
    (set! sum-divs (make-sum-divs max-sum-div)))
  (let loop ((s (vector-ref sum-divs n)) (cs (list n)))
    (cond ((= s n) (reverse cs))
          ((not (< n s limit)) (list))
          ((member s cs) (list))
          (else (loop (vector-ref sum-divs s) (cons s cs))))))

(define (chains limit)
  (when (< max-sum-div limit)
    (set! max-sum-div limit)
    (set! sum-divs (make-sum-divs max-sum-div)))
  (let loop ((n 2) (cs (list)))
    (if (< limit n) (reverse cs)
      (let ((c (chain n limit)))
        (if (null? c) (loop (+ n 1) cs)
          (loop (+ n 1) (cons c cs)))))))

> (sort (lambda (a b) (< (length a) (length b))) (chains 1000000))
((6) (28) (496) (8128) (220 284) (1184 1210) (2620 2924)
 (5020 5564) (6232 6368) (10744 10856) (12285 14595)
 (17296 18416) (63020 76084) (66928 66992) (67095 71145)
 (69615 87633) (79750 88730) (100485 124155) (122265 139815)
 (122368 123152) (141664 153176) (142310 168730)
 (171856 176336) (176272 180848) (185368 203432)
 (196724 202444) (280540 365084) (308620 389924)
 (319550 430402) (356408 399592) (437456 455344)
 (469028 486178) (503056 514736) (522405 525915)
 (600392 669688) (609928 686072) (624184 691256)
 (635624 712216) (643336 652664) (667964 783556)
 (726104 796696) (802725 863835) (879712 901424)
 (898216 980984) (12496 14288 15472 14536 14264)
 (14316 19116 31704 47616 83328 177792 295488 629072 589786
  294896 358336 418904 366556 274924 275444 243760 376736
  381028 285778 152990 122410 97946 48976 45946 22976 22744
  19916 17716))

四个完美的数字形成长度为1的友好链,有40个友好的对,上面提到了一个长度为5的友好链,并注意到长度为28的壮观的友好链,始于14316.

答案 2 :(得分:0)

我只是想在M和N之间找到友好的对

(define (find-amicable-pairs M N)
  (< M N)
  (define i M)
  (define a 0)
  (do ()
    [(= i N)] 
    (set! a (sum-of-divisors i))
    (if (and(= i (sum-of-divisors a)) (< i a))
        (and (display i)
             (display " and ")
             (display a)
             (newline))
        #f)     
    (set! i (+ i 1))))

感谢您对此的看法!