(define (multirember a lat)
(cond ((null? lat) '())
((eq? a (car lat))(multirember a (cdr lat)))
(else (cons (car lat)
(multirember a (cdr lat))))))
これは非常に綺麗なコードです。
実行結果は次のようになりますね。
> (multirember 'a '(a b c d a b c d))
(b c d b c d)
>
ところで、もう一度上のコードを見てみます。
(define (multirember a lat)
(cond ((null? lat) '())
((eq? a (car lat)) (multirember a (cdr lat))) ;1
(else (cons (car lat)
(multirember a (cdr lat)))))) ;2
まず特徴として、
- 分岐の木が
cond
により3本に分かれている。 (multirember a (cdr lat))
と言うのは実は書くと長い。
の二つがあります。
実は、この2番目が再帰のポイントなんで、「記述を重複するな」と言う言い方には無理があるんですけど、要するに記述のショートカットが出来るか否か、ってのがポイントではあるんです。
んで、実は、あまり知られてないんですが、再帰結果ってのは変数に束縛出来て使いまわしが可能なんです。
コード的には次のようになりますね。
(define (multirember a lat)
(if (null? lat)
'()
;; (car lat)も二回出てくるんでショートフォームitとして束縛
(let ((it (car lat))
;; 再帰部分もrecurと言う変数に束縛してしまう。
(recur (multirember a (cdr lat))))
(if (eq? a it) ;itの使いまわし
recur ;recurの使いまわし
(cons it recur))))) ;itとrecurの使いまわし
これでも全く同じように動作します。
> (multirember 'a '(a b c d a b c d))
(b c d b c d)
>
まあ、実際はあんま関係ないんですが、一応理論上は後者の方が若干効率が良い筈です。
と言うのも、Lisp系言語ほどコーディングスタイルが字面通り効率を表す言語は無いんじゃないか、と思われるから、です。
前者だと、例えば
(car lat)が一々書かれていると、ホントにその場で計算される為、その計算が文字通り二回行われます。反面、2番目のコードだと、再帰に付き1回しか計算されず、その結果は即時、変数itに束縛され「使いまわされ」ます。従って若干効率が上がるんです。
再帰部分も同じですね。結局「結果」が欲しいだけですし、その計算自体は全く同じ、なんで、実はコード内に律儀に2回書く必要はないのです。
この様に、分岐が3段階以上に分かれて、かつ、再帰部分のコード自体は「全く同じ」場合は、試しに局所変数として束縛しちゃうのがお薦めです。
注:ちなみに
call/cc
を用いて、
(define (multirember a lat)
(call/cc
(lambda (k)
(let ((it (car (if (null? lat)
(k '())
lat)))
(recur (multirember a (cdr lat))))
(if (eq? a it)
recur
(cons it recur))))))
と言う書き方も可能です。
元々、
'()
的な記述が必要なのは、Schemeが述語に関して#t
、#f
を返す、と言うメンド臭い仕様になってるから、ですが、上記の方法だと、一応コード本体の分岐の木は二本で済みます。ANSI Common Lispだと、
(defun multirember (a lat)
(and lat
(let ((it (car lat))
(recur (multirember a (cdr lat))))
(if (eq a it)
recur
(cons it recur)))))
ともうちょっとシンプルに書けますね。
0 件のコメント:
コメントを投稿