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 と引数が合わないという警告を出しているようです。