プロが教えるわが家の防犯対策術!

CLispを勉強し始めたところです。
リストを受け取ってその最大値を返す関数を作成したく、以下のようなプログラムを作成しました。
(defun l-max (l)
(let ((maxe (car l)))
(loop for x from 0 to (- (length l) 1)
do
(print maxe)
(print (nth x l))
(if (< maxe (nth x l)) (set 'maxe (nth x l)))
)
maxe
)
)
実行例は以下の通りです。
> (l-max '(1 3 2 5))

1
1
1
3
1
2
1
5
1

出力は
1
1
1
3
3
2
3
5
5
となると予想していたのですが、cond内がうまく処理されていないようです。
対処法を教えていただきたいです。よろしくお願いします。

A 回答 (1件)

まず、だな。



> リストを受け取ってその最大値を返す関数を作成

前提としてANSI Common Lispには既にmaxと言う関数がある。

Function MAX, MIN:
https://www.lispworks.com/documentation/HyperSpe …

基本的にANSI Common Lispには「何でもある」って思っていていい(笑)。
ANSI Common Lispはある意味「究極のエンジニアリング言語」を目指して作られてるんで、エンジニアリングツールとして見た場合、ほぼ「完璧な」関数群を備えてる・・・・・・そのため仕様書が電話帳みてぇになってるんだが(笑)。
んでmaxは基本的にはこう使う。

CL-USER> (max 1 3 2 5)
5

maxは可変長引数の関数だから、与えられた引数の中から最大値を返す。
・・・ただ、お題は「リストの要素の」最大値を返せ、って言ってるんだよな。
しかし問題はない。リストの要素の最大値を返したい場合、ANSI Common Lispではこう書けば良い。

CL-USER> (apply #'max '(1 3 2 5))
5

関数applyは第二引数のリストへ第一引数の関数を適用させる。言い換えると、リストの「カッコを外す」。

Function APPLY:
https://www.lispworks.com/documentation/HyperSpe …

#'(シャープクオート)ってのはちと気持ち悪い表現なんだけど、ANSI Common Lispでは変数と関数の名前空間が別々になっていて、これを付けないとmaxは単なる変数だと解釈されちまう。
従って、「これは関数ですよ」と教える為に#'が存在する。

つまり、お題の最短解自体は

(defun l-max (l)
 (apply #'max l))

になる、と言う事だ。
まずは「自分で何か書く」際に、ANSI Common Lispビルトイン関数を調べる、と言うクセを付けよう。まぁ何らかは「大体ある」。それらを「パーツとして」利用して関数を組み上げるのが一番ラクで、かつ効率がいい。
取り敢えず、何かあったらCommon Lisp HyperSpecを調べるようにしよう。

Common Lisp HyperSpec:
https://www.lispworks.com/documentation/HyperSpe …

さて、提示コードだけど、問題はsetだ。こりゃあんまり使わん。

Function SET:
https://www.lispworks.com/documentation/HyperSpe …

こう書いてある。

Notes:

The function set is deprecated.

set cannot change the value of a lexical variable.

訳: 関数setは非推奨です。setはレキシカル変数の値は変更出来ません。

「レキシカル変数」が何か、と言うと、提示されたコードではletで束縛された変数、つまりmaxeだ。maxeはsetで変更出来ない、んで提示された出力になってるんだ。
言い換えるとsetを使ってもmaxeは変更出来ないんで、ずーっとmaxeは1のままになっている。
通常、現代のCLerはsetfマクロを使う。

Macro SETF, PSETF:
https://www.lispworks.com/documentation/HyperSpe …

(defun l-max(l)
 (let ((maxe (car l)))
  (loop for x from 0 to (1- (length l)) ;; 1- と言う名の関数がある
     do (print maxe)
      (print (nth x l))
      (if (< maxe (nth x l)) (setf maxe (nth x l)))) ;; setfにする
  maxe))

そうすれば貴方の予想通りの動きになる。

CL-USER> (l-max '(1 3 2 5))

1
1
1
3
3
2
3
5
5

なお、拡張loopの使用方法は少々厄介だ。貴方が書いたコードはちと無駄が多い。
恐らく、拡張loopを使うとしたら、本当に書きたいコードは次のようになるんじゃないか。

(defun l-max(l)
 (loop with maxe = (car l) ;; with でローカル変数を設定出来る
    for x in l ;; in でリストlから順次要素を引っこ抜いてxに束縛可能
    do (print maxe)
     (print x)
    when (< maxe x) ;; when と言う条件節がloopには用意されている
     do (setf maxe x)
    finally (return maxe))) ;; finally節で何を返したいか指定可能

拡張loopの愛用者が多いのは事実だけど、ちとCOBOL的な冗長さを感じる事は否めない。
ぶっちゃけ、もっと良いのはreduceを使う事だと思う。

Function REDUCE:
https://www.lispworks.com/documentation/HyperSpe …

(defun l-max(l)
 (reduce #'(lambda (x maxe)
      (print maxe)
      (print x)
      (if (< maxe x) x maxe)) l :initial-value (car l)))

これは出力結果は違ってくるが、一方計算自体は正しく行われる。

CL-USER> (l-max '(1 3 2 5))

1
1
3
1
2
3
5
3
5

「出力結果が違う」のは、こっちはmaxeを「破壊的変更をしてない」事に拠る。
setfは「変数の値を書き換える」が、reduceは条件に応じてxとmaxeの値を「すげ替えている」だけで「書き換え」はしてないんだ。よって、条件に一致しない時にはmaxeの値は前回の値を「そのまま持っている」事となる。
いずれにせよ、これが「関数型プログラミング」で、「値を書き換えない」モダンなプログラミングのやり方、だろう。

でも、出力しないのなら、やっぱり最初に提示したapplyとmaxの組み合わせが一番シンプルで「適切な解」なんじゃないか、と思う。
    • good
    • 0
この回答へのお礼

とても分かりやすく助かりました!
大変勉強になりました。
ありがとうございました!!

お礼日時:2024/05/12 17:18

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています


このQ&Aを見た人がよく見るQ&A