[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [bep] 割り算の答えを小数で出すには? <Re: learning lisp: while



 r高橋です。

 ひさよしまへの道はたいへん険しく……ではなくて、まだその前のところにい
ます(^^;;;)。
昼間&放課後がちょっとだけ立て込んでいたのと、夜遅くスターバックスのアイ
スグランデラテを一気飲みしておなかを壊したのと、夜よりは強いはずの朝なの
に全然起きられなかったのと……いろいろあって遅くなりました。

Reply TAKAHASHI Naoto <ntakahas@...>'s message:

} > (let ((x 1000) (y 3))
} >   (while 
} >       (or (> x 3) (/= (% x 2) 0))
} >     (while 
} > 	(and (<= y (+ (/ x 2) 1))  
} > 	     (/= (% x y) 0))
} >       (setq y (+ y 1))
} >       (when (= y (+ (/ x 2) 1)) 
} > 	(insert (format "%s\n" x)))) 
} >     (setq x (- x 1))
} >     (setq y 3))
} >   (insert (format "%s\n" 3))  
} >   (insert (format "%s\n" 2)))
} 
} 外側の while における条件 (or (> x 3) (/= (% x 2) 0)) は、多分望んでらっ
} しゃるのとは違う働きをしていると思います。多分意図としては、「3より大
} きくてかつ奇数の場合」だけループを実行しようということじゃないかと思う
} んですが、違うでしょうか。

 きゃあっ、そうでした!

} 実際には、外側の while は次のように動くことになります。
} 
} 1. xが1000から4の間は、xを1ずつ小さくしながらループを回る。
} 
} # 関数 or は、最初に t になった時点で残りを評価せずに終了します。です
} # からxが3より大きい場合 (> x 3) は、後ろの /= は無視されます。
} 
} 2. xが3のときは、or の第2式が t になるのでやはりループを実行する。
} 
} 3. xが2になると、or の第2式が nil を返すので、外側の while が終了する。
} 
} つまり外側の while の条件式は、結局 (> x 2) と同じことになっています。

 ほんと、そうですね。
ちゃんと覚えてはいないのですが、最初、ここを
(and (> x 3) (/= (% x 2) 0))
にしたらおかしなことになって、もう時間もないのでどさくさで(or)にしたら一
応それらしい答えを表示してくれたので、「えい、これでいいや!」とポストし
てしまいました。
でも、どうして(and)だとだめだったんでしょう……? 今ゆっくり見てもわかり
ません。
でも、答えはおかしくなります(3 と 2だけになる?)。

} もし「3より大きくてかつ奇数の場合」だけを調べたいのなら、
} 
} (let ((x 999) (y 3))
}   (while (> x 3)
}     内側の while
}     (setq x (- x 2))
}     (setq y 3))
}   最後の insert 2つ)
} 
} のようにします。最初を999という奇数で始め、それから2ずつ引いていけば奇
} 数だけをチェックするようになります。

 なるほど、わかりました。

} 次に内側のループですが、ここには無駄があります。最後の when とそれに続
} く insert は、while の外に出せます。
} 
} (while
}   (and (<= y (+ (/ x 2) 1))
}        (/= (% x y) 0))
}   (setq y (+ y 1)))
} (when (= y (+ (/ x 2) 1))
} (insert (format "%s\n" x))
} 
} この while ループは、y が上限値以下で、かつ x が y で割り切れない間ずっ
} と続きます。反対に言うと、この while が終了するのは y が上限に達したか、
} あるいは x が y で割り切れた場合です。while が終了した後も y は最後の
} 値を保持したままですから、ループの中で毎回 when のチェックをする必要は
} ありません。ループが終わった後で1回やれば充分です。

 ああ! そうですね!
(and (<= y (+ (/ x 2) 1)) ...) と
(when (= y (+ (/ x 2) 1)) ...) は、同じことをチェックしてた……。
気も知的には、(and ...)のほうは、まだ続けるかどうかを判断するためのチェッ
ク、(when ...)のほうは、素数として表示するかどうかのチェックだったのです
が……。すごい目から鱗です! でも、私はこれからもこういう無駄をたくさん
してしまいそうです……。

} それから他のスレッドで話題になっていますが、y で割り切れるかどうかチェッ
} クするときの上限値は (+ (/ x 2) 1) ではなくて、(sqrt x) です。sqrt は
} square root (平方根) の略です。x が2以上の場合、(sqrt x) は常に
} (+ (/ x 2) 1) よりも小さくなりますから、ループを回る回数をさらに減らす
} ことができます。

 これ、どうしてそう言えてしまうのかが未だによくわかりません。
割る数が平方根になったとき、答えも平方根になるから、それ以上大きな数で割
っても、前に割ったことのある小さな数が答えになるだけで、結局同じことを二
度やってしまっている……ということでしょうか??
……あっ、それなら今なんとなくわかったような気が……。
でも、平方根って整数ではないですよね。その場合は、切り上げした整数を使え
ばいいですか?
平方根を整数にするには、どうすればいいでしょう?(って、みなさんが書いた
のを見ればいいですね(^^;;;))。

} えーと、2とか3の場合ってそんなに面倒ですか?  素直に書けると思うのです
} が…。ちょっとやってみましょう。
} 
} ;; n高橋バカ正直バージョン
} ;; 2から1000まで全部調べる
} ;; xが素数かどうかは、2からx-1までの全部の数で割って調べる
} (let ((x 2) y)                          ; 素数かどうか調べる数(x)の初期値は2
}   (while (<= x 1000)                    ; xの最大値は1000
}     (setq y 2)                          ; 最初に割ってみる数(y)は2
}     (while (and (< y x) (/= 0 (% x y))) ; yがxより小さくてかつ割り切れない間は
}       (setq y (1+ y)))                  ;   yを1増やして次のチェック
}     (when (= y x)                       ; y=xならば全部のyで割り切なかったはず
}       (insert (format "%s\n" x)))       ; つまりxは素数
}     (setq x (1+ x))))                   ; 次のxを調べる

 こんなにすっきりできるんですね。私が書くと、頭の中のぐちゃぐちゃがその
まま出てきちゃうみたいです。ずるをしなくても大丈夫だし、づごい……!!

 ところで、(let ...)で、使う変数を指定するとき、そこに値を当てはめなくて
もいいんですね。私は、必ずなにかの値を入れなければならないと思っていまし
た。
(let ((x 2) y) ...) → (let ((x 2) (y 0)) ...) みたいに。

} ;; n高橋スマートバージョン
} ;; 2は特別扱いして、奇数だけ調べる
} ;; xが素数かどうかは、(sqrt x) までの奇数で割って調べる
} (let ((x 3) y limit)                    ; 3から調べ始める
}   (insert "2\n")                        ; ここだけずるをする
}   (while (<= x 1000)
}     (setq y 3 limit (sqrt x))           ; 試し割りは3からルートxまで
}     (while (and (<= y limit) (/= 0 (% x y))) ; limitも試すので<=と等号が必要
}       (setq y (+ y 2)))                 ; 試し割りは奇数だけなので+2
}     (when (> y limit)                   ; limitで割り切れたときは合成数
}       (insert (format "%s\n" x)))       ;   だから>=でなくて>
}     (setq x (+ x 2))))                  ; 次の奇数を試す

 (when (> y limit)                   ; limitで割り切れたときは合成数
というのがわかりません。
「合成数」というのは、「1 と自分自身以外の約数をもつ整数。二つ以上の素数
の積。非素数」なんですね(初めて知りました(^^;))。
「limitで割り切れたとき」というのは、たとえば9とか16とか25とか、そういう
場合のことですか?
あっ、もしかして、さっき書いた、平方根が整数だった時とそうじゃなかった時
の処理に関係している……?

} 調子に乗ってさらに高速化。

 これは難しいので30年後に(^_^)。

 なんだか、考えたことをだらだら書いてしまってすみません。
でも、もしかして、どこかですごーく道を誤っているかもと思うので、そのまま
ポストさせてください。

 次は、ほんとうに、

} それじゃあ次に、(primes n) とするとnまでの素数を全部求め、それをリスト
} にして返すような関数 primes を作ってみて下さい。最初の方で 

をやってみたいと思います。


**-***-***-***-***-***-***-***-***-***-***-***-**
           Reiko TAKAHASHI  (高橋玲子)
         E-mail:  HFC03614@...
         ICQ UIN: 85924121  (Twinkle)
**-***-***-***-***-***-***-***-***-***-***-***-**