プログラミング再入門

プログラミングをもう一度ちゃんと勉強する読書ノート

Scheme手習い 第4章 数遊び(その2)

その他の数に関連する関数を習います。

ノート:

Macで使っているDrRacket上では>と>は見間違え様の無い程異なって見えるので、全角の>を使用する。

最初の定義で評価する。

> ( 12 133)
#f
> ( 120 11)
#t
> ( 3 3)
#t
> 

nとmが等しい時に間違う。

修正版の定義はあまり直感的とは言えない(と思う)。

  1. 正の整数のみを扱っているので、n、mはそれぞれ0か0より大きい。
  2. この状況でnが0であれば、nは少なくともmより大きくはないので偽。
  3. そうで無い場合既にnは0より大きい事は分かっているので、mが0であればnの方が大きいので真

と解釈する。

> ( 12 133)
#f
> ( 120 11)
#t
> ( 3 3)
#f
> 

同様に

> ( 4 6)
#t
> ( 8 3)
#f
> ( 6 6)
#f
> 

最初の定義はまた直感的とは言えない。

  1. mが0の場合は、nが0か否かに依存する。即ちnが0の場合は真であり、その場合m、nとも0で等しいので(= m n)も真。nが0でない場合は偽であり、この場合nとmは等しくないので偽。
  2. そうでない場合、即ちmが0より大きい場合に、nが0の場合は、mとnは等しくないので偽

と解釈する。

最初の定義で評価。

> ( 1 2)
#f
> ( 4 3)
#f
> ( 5 5)
#t
> 

二番目の定義は分かりやすいが、効率的とは言えなさそう。ここでは区別のために==を使う。

> (== 1 2)
#f
> (== 4 3)
#f
> (== 5 5)
#t
>

数のアトムには=を、そのほかにはeq?を使います。

DrRacketでは(eq? 2 2)は#tを返すが、R5RSのテキストでは「未規定」となっている。引数が数の場合は=を使用するのが正しい。ちなみに=、>、<と言った関数の引数は二つ以上。

> (= 2 2)
#t
> (eq? 2 2)
#t
> (eq? 2 2 2)
. . eq?: expects 2 arguments, given 3: 2 2 2
> (= 2 2 2)
#t
> (< 1 2 3)
#t
> (< 2 3 1)
#f
> 

べき乗。数学的に自然な定義のままのプログラム。第五の戒律:×で値を作るときの基底の値は1。0乗は常に1なので定義とも一致。

> ( 1 1)
1
> ( 2 3)
8
> ( 5 3)
125
> 
÷

第五の戒律:+で値を作るときの基底の値は0。割る数が割られる数よりも大きい場合の商は0。
全体の流れとしては以下の理解となるが、

  1. 割られる数から割る数を、割る数よりも小さくなるまで引く
  2. 引いた回数を数える

関数自体の定義は以下の様に理解する。

  1. 割られる数が割る数よりも小さければ商は0
  2. 商は、割られる数から割る数を引いた値を割る数で割った値+1
> (÷ 15 4)
3
> (quotient 15 4)
3
>

組み込み関数ではquotient(商)。割り算(/ 15 4)を計算すると3¾と表示される。

length

lengthは組み込み関数が存在しているので、ここではnagasaと言う名前で定義する。
定義は

  1. リストが空なら長さは0
  2. 空でなければ、先頭要素を除いた残りのリストの長さ+1

と解釈する。

> (let ((lat '(hotdogs with mustard sauerkraut and pickles)))
    (nagasa lat))
6
> (let ((lat '(ham and cheese on rye)))
    (nagasa lat))
5
> 
pick

数に関連しているが、数字を返すとは限らない。リストの要素を返す。
C言語の配列の様に0番目から始める方式ではなく、自然に最初の要素を1番目と数えてn番目の要素を返す。
1と比較する関数を持ち合わせていないので、1を引いたら0になるかで判断している。
定義は

  1. nが1なら(1引いて0になるなら)リストの先頭要素
  2. そうでなければ、先頭要素を除いたリストの残りのn-1番目

と解釈。

 (let ((n 4)
        (lat '(lasagna spaghetti ravioli macaroni meatball)))
    (pick n lat))
macaroni
> (let ((lat '(a)))
    (pick 0 lat))
. . mcdr: expects argument of type <mutable-pair>; given ()
> 

(pick 0 ...)に対して、この章ではまず(sub1 0)が定義されていない事になっている。実際の環境では-1となってこの条件を抜けてしまい、1回再帰して(pick -1 '())で呼ばれ、(sub1 -1)は0ではないのでelseに抜けて次の(cdr '())でエラーで止まる。

rempick

指定された位置の要素以外の要素からなるリスト。元のリストからremoveする訳ではない。
定義は

  1. nが1なら、先頭要素を除いた残りのリスト
  2. nが1より大きければ、リストの先頭要素と、リストの残り要素から(n - 1)番目の要素を除いたリストを連結したリスト

と解釈する。

> (let ((n 3)
        (lat '(hotdogs with hot mustard)))
    (rempick n lat))
(hotdogs with mustard)
> 

指定した数がリストの要素数よりも大きいとエラーとなる。

> (let ((n 5)
        (lat '(hotdogs with hot mustard)))
    (rempick n lat))
. . mcdr: expects argument of type <mutable-pair>; given ()
> 
number?

number?はアトムのうちのある一部で#tになり、他の関数では定義出来ない気がする。

> (let ((a 'tomato))
    (number? a))
#f
> (number? 76)
#t
>
no-nums

数以外の要素からなる新しいリスト。
定義は:

  1. 先頭要素が数であれば、残りの要素から数を除いたリスト
  2. 先頭要素が数でなければ、先頭要素と残りの要素から数を除いたリストを連結したリスト

と解釈する。

> (let ((lat '(5 pears 6 prunes 9 dates)))
    (no-nums lat))
(pears prunes dates)
> 
all-nums

ラットからタップを取り出す関数です。

「取り出す」は、元のリストには残したまま、元のリストに含まれる数の要素だけからなる新しいリスト。
定義は:

  1. 元のリストが空リストであれば、空リスト
  2. 元のリストの先頭要素が数字であれば、その先頭要素と元のリストの残り要素からタップを抜き出したリストを連結したリスト
  3. そうでなければ、元のリストの残り要素からタップを抜き出したリスト
> (let ((lat '(5 pears 6 prunes 9 dates)))
    (all-nums lat))
(5 6 9)
eqan?

数字の比較には=、それ以外の比較にはeq?を用いる汎用比較関数。

> (eqan? #t #t)
#t
> (eqan? 'a 'a)
#t
> (eqan? '(1 2 3) '(1 2 3))
#f
> (eqan? (car '(1 2 3)) (car '(1 2 3)))
#t
> (eqan? (add1 1) (sub1 3))
#t
> (let ((a 1) (b 1)) (eqan? a b))
#t
> 

DrRacketでは=は数字のみの比較(ただし引数の数は二つ以上)、eq?、eqv?、equal?はeqanと同様に働く。

occur

リスト内の出現回数を数える。
定義は:

  1. リストが空なら0回
  2. 先頭要素が探している要素であれば、リストの残り要素内の出現回数+1
  3. そうでなければ、リストの残り要素内の出現回数
> (occur 1 '(1 2 3 1 2 3 1 2 3))
3
> 

リストが空でなければ、どの道リストの残り要素内の出現回数を得るので、プログラムに重複があり一見無駄がある様に見えるが、どちらかにしか入らないのでパフォーマンス的な無駄が生じている訳ではない。

one?

最初にやや不思議な定義が示される。再帰はしていないので基底部を持つ訳ではないが、nが0の場合(sub1 n)が定義されないので、sub1の前にnが0より大きい事を保証する必要がある。

one?で再定義したrempickで評価してみる。

> (let ((n 3)
        (lat '(lemon meringue salty pie)))
    (rempick n lat))
(lemon meringue pie)
>