Project Euler Problem 31

問題31: イギリス硬貨 1ペンス〜2ポンドを使い 2ポンドをつくる方法は何通りあるか。[=>問題文]

問題31の解答

;; Emacs lisp
(require 'cl)

(defun problem031a (n)
  (labels
      ((repeat (coins acc)
               (if (null coins)
                   0
                 (do ((add (car coins))
                      (next-coins (cdr coins))
                      (count 0))
                     ((>= acc n) (if (= acc n) (1+ count) count))
                   (incf count (repeat next-coins acc))
                   (incf acc add)))))
    (repeat '(200 100 50 20 10 5 2 1) 0)))

(problem031a 200)

 組み合わせパターンの生成は簡単でしたが、count の置き方、受け渡し方に迷いました。do の終了処理周りがいまいちスッキリしませんが・・・とりあえず答えは出たのでこれで・・・。
 ところでいろいろコードをいじってて思ったんですが、なんか labels って遅くないですか? というわけで実験。
 ロジックは全部同じです

(require 'cl)

;; flet 版
(defun problem031b (n)
  (flet
      ((repeat (coins acc)
               (if (null coins)
                   0
                 (do ((add (car coins))
                      (next-coins (cdr coins))
                      (count 0))
                     ((>= acc n) (if (= acc n) (1+ count) count))
                   (incf count (repeat next-coins acc))
                   (incf acc add)))))
    (repeat '(200 100 50 20 10 5 2 1) 0)))

;; let + lambda 版
(defun problem031c (n)
  (let ((repeat
         (lambda (coins acc)
           (if (null coins)
               0
             (do ((add (car coins))
                  (next-coins (cdr coins))
                  (count 0))
                 ((>= acc n) (if (= acc n) (1+ count) count))
               (incf count (funcall repeat next-coins acc))
               (incf acc add))))))
    (funcall repeat '(200 100 50 20 10 5 2 1) 0)))

 例によって Core2 Quad 6600 にて計測。

Function      Avg (sec)
===========   ==========
problem031a   3.822200  labels
problem031b   0.959400  flet
problem031c   1.037200  let + labmda

 やっぱり labels は遅かった。labels と flet はどちらも cl パッケージのマクロです。レキシカルスコープの実装がオーバーヘッドになっているんでしょうか?

コンパイル時の警告

 let とflet はコンパイル時(compile-defun)に警告が出ます。

;; flet
Warning: repeat called with 2 arguments, but accepts only 1
Warning: repeat called with 2 arguments, but accepts only 1

;; let + lambda
Warning: reference to free variable `repeat'

 後者は、let + lambda で再帰を書くといつもでる警告ですが、前者はどういうことなんでしょう? 引数が 1 つしか accept されない? よくわかりません。

追記: repeat は Emacs Lisp で定義済みの関数でした。直前のコマンドを繰り返す機能です。上記 3 つの関数では flet だけが、グローバルな関数を参照するため、既存の repeat と引数が合わないという警告を出しているようです。