リスト修行 約数のリスト(2)

素因数分解まで出来ちゃいました、素因数分解から組み合わせ(部分集合の集合)を求めて再び約数に挑戦します。

組み合わせ

確か練習問題(2.32)にあったなぁと思いつつ。SICPをめくる。

あぁ、全然理解できなかった奴だ・・・。

頑張って理解しよう。ちょっと汚いけど、コメントしておく。

; 0. (subsets '(1 2 3))の時、
(define (subsets s)
  (if (null? s)
      ; 3. 最後まで辿り着いたら '(())を返して
      (list '())
      ; 2. cdrダウン
      (let ((rest (subsets (cdr s))))
        ; 4. 下から拾ってきたリストとappeed
        ;    '(()) + (map 3をcons '()),
        ;    '(() (3)) + (map 2をcons '(() (3))))
        ;    '(() (3) (2) (2 3)) + (map 1をcons '(() (3) (2) (2 3))))
        ;    '(() (3) (2) (2 3) (1) (1 3) (1 2) (1 2 3)) 終了。
        (append rest (map
                       (lambda (x)
                         ; 1. (car s) [1, 2, 3]を置いといて
                         (cons (car s) x))
                       rest)))))

ふぅ。理解できた。

fold-rightを使って書き直してみる。

(define (subsets s)
  (fold-right
    (lambda (a b)
      (append b
              (map (lambda (x)
                     (cons a x))
                   b)))
    '(()) s))

うっほ。セクシーに書けた。簡単だった。


組み合わせ(部分集合の集合)すると、

(factor 50)
; (2 5 5)
(subsets (factor 50))
; (() (5) (5) (5 5) (2) (2 5) (2 5) (2 5 5))

おぉぉぉ。これだよコレ。後は掛算すればいい。

約数を求める

最終章だ。最終章。やっと辿り着いた!!

(define (divisor n)
  (sort
    (unique (map (lambda (m)
                   (fold * 1 m))
                 (subsets (factor n))))
    <))

uniqueとsortを使って成形してっと。

50の約数を求めます。

(divisor 50)
; (1 2 5 10 25 50)


でたぁ〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜約数だよ。約数。


大きい数もやってみよう。

(divisor 1000000)
; (1 2 4 5 8 10 16 20 25 32 40 50 64 80 100 125 160 200 250 320 400 500
;  625 800 1000 1250 1600 2000 2500 3125 4000 5000 6250 8000 10000 12500
;  15625 20000 25000 31250 40000 50000 62500 100000 125000 200000 250000
;  500000 1000000)

100万の約数だって一瞬で出る。はえぇぇぇよぉ。

まとめ

約数を求めるのは大変だった。

  1. 素数を求める
  2. 素因数分解する
  3. 組み合わせ(部分集合の集合)を求めて掛算
  4. ユニーク&ソート。しかし、ユニーク、ソートを使わなくても出来そうな気がする。改善の余地あり。

疲れた。

でも、SICPの問題subsetsをやっと理解できた。ちょっと置いといて、後でconsだ。つまり、fold-right。


やっぱり素数は楽しいね。