ペアとは2つのデータをまとめた構造体のことである。ドットペアと呼ばれることもある。
ペアに対する基本操作を以下にまとめる:
以下を Definitions に記述し、Run してみよう。
(define p1 (cons 100 200)) (car p1) (cdr p1) |
ペアを組み合わせて、データを並べた構造体をリストと呼ぶ。
たとえば、次のようにペアを使うと、数字のリストになる。
(define p2 (cons 1 (cons 2 (cons 3 (cons 4 ()))))) |
() は空を表す
このように、リストの正体は、ペアの集りであるが、分りにくい。そこで、次のように書くことができる。
(define p3 (list 1 2 3 4)) |
equal? を使って p2 と p3 が等しいことを確認しよう。
> (equal? p2 p3) |
リストに対する基本操作を以下にまとめる:
リストの個々のデータを扱う関数を作ってみよう。
(define list-sum (lambda (lst) (if (null? lst) 0 (+ (car lst) (list-sum (cdr lst)))))) |
実行方法: (list-sum (list 1 2 3 4 5))
予想結果: 15
(define list-print (lambda (lst) (if (not (null? lst)) ((lambda () ここの lambda は (write '<) これら write と list-print の順接を (write (car lst)) 実行するために (write '>) 使われている。 (list-print (cdr lst))))))) lambda の前に括弧が2つあることに注意 |
実行方法: (list-print (list 1 2 3 4 5))
予想結果: <1><2><3><4><5>
(define list-max (lambda (lst) (if (null? (cdr lst)) もし長さが1のリストならば、 (car lst) リストの先頭の数値が最大値である。 ((lambda (m n) そうでなければ、2つの数値 m と n について (if (> m n) m n)) 大きい数値を返す関数に、 (car lst) リストの先頭の数値と (list-max (cdr lst)))))) リストの2番目以降の最大値を比較してもらう。 |
実行方法: (list-max (list 1 2 4 5 3))
予想結果: 5
上記の list-max の意味がわからない人は以下の定義をまず考えてみよう。
(define greater (lambda (m n) (if (> m n) m n))) (define list-max (lambda (lst) (if (null? (cdr lst)) (car lst) (greater (car lst) (list-max (cdr lst)))))) |
以下の定義でも動作する。
ところで、関数型言語ならば、引数が同じ関数呼出しは同じ答えが得られるはずだから、無駄な計算は発生しない? ⇒ ダウト。その保証はない! 悩んでください。
(define list-max (lambda (lst) (if (null? (cdr lst)) (car lst) (if (> (car lst) (list-max (cdr lst))) (car lst) (list-max (cdr lst)))))) |
上記の例は、リストから値を抽出する関数であった。次にリストを返す関数を作ってみよう。
(define my-append (lambda (lst1 lst2) (if (null? lst1) lst2 (cons (car lst1) (my-append (cdr lst1) lst2))))) |
実行方法: (my-append (list 1 2 3) (list 4 5 6))
予想結果: (1 2 3 4 5 6)
実行方法: (my-append (cons 1 (cons 2 ())) (cons 3 (cons 4 ())))
予想結果: (1 2 3 4)
(define list-double (lambda (lst) (if (null? lst) () ← 空を表す。(list) と同じ。 (cons (* 2 (car lst)) (list-double (cdr lst)))))) |
実行方法: (list-double (list 1 2 3 4 5))
予想結果: (2 4 6 8 10)
新しく作られるリストの各数値は、入力のリストの要素において隣り合う2つの数値の和とする。たとえば,(1 1) を入力すると (2) を返し、(1 2 1) を入力すると (3 3)を返す。
ただし、入力のリストの要素数が2未満のときは空のリストを返すこととする。
(define add-next (lambda (lst) (if (< (length lst) 2) (list ) (cons (+ (car lst) (car (cdr lst))) (add-next (cdr lst)))))) |
実行方法: (add-next (list 1 2 1))
予想結果: (3 3)
リスト操作と再帰呼出しに慣れるために、以下の関数を作ってみよう。できるところからやってみよう。
ヒント:1 つの問につき、関数を 2 つ作ると簡単。
よく使うプリンタの用紙は、A4サイズである(210mm x 297mm)。この用紙は、長辺を半分に折るとA5サイズの用紙になる(小数点の切り捨てはtruncate関数を使用)。A0サイズは、841mm x 1189mm という大きさである。では、nを変数として Anサイズの2辺の長さをペアの値で返す関数 (paper-a-size n) を定義せよ。
ヒント:ペアのデータ1を短辺、データ2を長辺とすることにする。
ヒント:ペアのデータを入力として、長辺を折り曲げて新しい短辺とし、短辺を新しい長辺としてペアの値を返す関数を作ってみる。
問題を説明する前に、次の式を見てみよう。
右辺の係数に注目すると、次の図のような関係が見えてくる。この図をパスカルの三角形という。
(a+b)の羃乗の数ごとに、係数をリストで表すと、次のようになる。
(a+b)nのときの係数のリストを返す関数 list-pascal(n) を定義せよ。
ヒント:list-pascal では、n = 1 のとき、「(list 1 1)を返すこと」として、そのほかのとき、「n - 1 の list-pascal の返り値について add-next を計算した結果の前後に 1 を付けたもの」とするとよい。つまり,n=3 の (1 3 3 1) を求める計算では、n=2 の list-pascal の返り値である (1 2 1) から、add-next を用いて (3 3) を求めて、前後に (1) を append すると実現できる。
できた人は、append を使わずに、cons を使って関数 list-pascal を定義しよう。
(注意:add-next の使用は各自の自由である。)