M.Hiroi's Home Page

Common Lisp Programming

Yet Another Common Lisp Problems

[ PrevPage | Common Lisp | NextPage ]

はじめに

今回はちょっと便利な関数を問題形式で紹介します。元ネタは P-99: Ninety-Nine Prolog Problems です。問題は拙作のページ Prolog Programming Yet Anotehr Prolog ProblemsScheme Programming Yet Another Scheme Problems と同じですが、あしからずご了承くださいませ。

なお、解答では Common Lisp のループ機能 (いわゆるループマクロ) は使っておりません。ループマクロを使ったほうが簡単に解ける問題もあるかと思います。興味のある方はループマクロでプログラムを作ってみてください。

●問題1

リストの要素がただひとつか調べる述語 singlep を定義してください。

> (singlep '(a))
t
> (singlep '(a b))
nil
> (singlep '())
nil

解答

●問題2

リストの要素が二つあるか調べる述語 doublep を定義してください。

> (doublep '(a b))
t
> (doublep '(a b c))
nil
> (doublep '(a))
nil

解答

●問題3

リスト xs はリスト ys よりも長いか調べる述語 longerp xs ys を定義してください。

> (longerp '(a b c) '(a b))
t
> (longerp '(a b) '(a b))
nil
> (longerp '(a) '(a b))
nil

解答

●問題4

リスト xs の最後尾を求める関数 last と、最後尾の要素を取り除く関数 butlast を定義してください。Common Lisp には同等の機能を持つ関数 last と butlast がありますので、ここでは関数名を my-last と my-butlast としました。

> (my-last '(a b c))
(c)
> (my-last '(a))
(a)
> (my-butlast '(a b c))
(a b)
> (my-butlast '(a))
()

解答

●問題5

リスト xs の先頭から n 個の要素を取り出す関数 take xs n を定義してください。

> (take '(a b c d e) 3)
(a b c)
> (take '(a b c d e) 0)
()
> (take '(a b c d e) 6)
(a b c d e)

解答

●問題6

リスト xs の先頭から n 個の要素を取り除く関数 drop xs n を定義してください。なお、Common Lisp には同等の機能を持つ関数 nthcdr があります。

> (drop '(a b c d e) 3)
(d e)
> (drop '(a b c d e) 0)
(a b c d e)
> (drop '(a b c d e) 6)
()

解答

●問題7

リスト xs の n 番目から m - 1 番目までの要素を部分リストとして取り出す関数 subseq xs n m を定義してください。なお、リストの要素は 0 から数え始めるものとします。Common Lisp には同等の機能を持つ関数 subseq がありますので、ここでは関数名を my-subseq としました。

> (my-subseq '(a b c d e) 2 4)
(c d)
> (my-subseq '(a b c d e) 0 5)
(a b c d e)
> (my-subseq '(a b c d e) 0 0)
()

解答

●問題8

リスト xs の末尾から n 個の要素を取り除くことができるように関数 butlast を拡張してください。

butlast ls &optional (n 1)
> (my-butlast '(a b c d e) 3)
(a b)
> (my-butlast '(a b c d e) 0)
(a b c d e)
> (my-butlast '(a b c d e) 5)
()

解答

●問題9

リスト xs を長さ n の部分リストに分割する関数 group xs n を定義してください。

> (group '(a b c d e f) 2)
((a b) (c d) (e f))
> (group '(a b c d e f) 3)
((a b c) (d e f))
> (group '(a b c d e f) 4)
((a b c d) (e f))

解答

●問題10

リスト ls の中から x と等しい要素の位置 n を求める関数 position x ls を定義してください。なお、リストの要素は 0 から数え始めるものとします。Common Lisp には同等の機能を持つ関数 position があるので、ここでは関数名を my-position としました。

> (my-position 'a '(a b c d e))
0
> (my-position 'c '(a b c d e))
2
> (my-position 'e '(a b c d e))
4
> (my-position 'f '(a b c d e))
nil

解答

●問題11

リスト ls から x と等しい要素の個数 n を求める関数 count x ls を定義してください。Common Lisp には同等の機能を持つ関数 count があるので、ここでは関数名を my-count としました。

> (my-count 'a '(a b a b c a b c d))
3
> (my-count 'c '(a b a b c a b c d))
2
> (my-count 'd '(a b a b c a b c d))
1
> (my-count 'e '(a b a b c a b c d))
0

解答

●問題12

リストの要素の合計値を求める関数 sum-list を定義してください。

> (sum-list '(1 2 3 4 5))
15

解答

●問題13

リストの中から最大値を求める関数 max-list と最小値を求める関数 min-list を定義してください。

> (max-list '(5 6 4 7 3 8 2 9 1))
9
> (min-list '(5 6 4 7 3 8 2 9 1))
1

解答

●問題14

リスト ls の中で要素 x の右隣に要素 y があるかチェックする述語 adjacent x y ls を定義してください。

> (adjacent 'a 'b '(a b c d e f))
t
> (adjacent 'e 'f '(a b c d e f))
t
> (adjacent 'f 'e '(a b c d e f))
nil

解答

●問題15

リスト ls の中で要素 x が 要素 y よりも前に出現しているか調べる述語 before x y ls を定義してください。

> (before 'a 'b '(a b c d e f))
(b c d e f)
> (before 'c 'b '(a b c d e f))
nil

解答

●問題16

整数 n から m までを格納したリストを作る関数 iota n m を定義してください。

> (iota 1 5)
(1 2 3 4 5)

解答

●問題17

リストから重複要素を取り除いて集合を生成する関数 set-of-list を定義してください。

> (set-of-list '(a b c d e f a b c))
(d e f a b c)

解答

●問題18

2 つの集合の和を求める関数 union を定義してください。なお、Common Lisp には同等の機能を持つ関数 union があるので、ここでは関数名を my-union としました。

> (my-union '(a b c d) '(c d e f))
(a b c d e f)

解答

●問題19

2 つの集合の積を求める関数 intersection を定義してください。なお、Common Lisp には同等の機能を持つ関数 intersection があるので、ここでは関数名を my-intersection としました。

> (my-intersection '(a b c d) '(c d e f))
(c d)

解答

●問題20

2 つの集合の差を求める関数 difference を定義してください。なお、Common Lisp には同等の機能を持つ関数 set-difference があります。

> (difference '(a b c d) '(c d e f))
(a b)

解答

●問題21

2 つのソート済みのリストをひとつのソート済みのリストにまとめる関数 merge-list を定義してください。なお、Common Lisp には同等の機能を持つ関数 merge があります。

> (merge-list #'< '(1 3 5 7) '(2 4 6 8))
(1 2 3 4 5 6 7 8)

解答

●問題22

関数 merge-list を使ってリストをソートする merge-sort を定義してください。なお、Common Lisp には列 (リスト、ベクタ、文字列) をソートする関数 sort があります。

> (merge-sort #'< 9 '(5 6 4 7 8 3 2 9 1 10))
(1 2 3 4 5 6 7 8 9)
> (merge-sort #'< 10 '(5 6 4 7 8 3 2 9 1 10))
(1 2 3 4 5 6 7 8 9 10)
> (merge-sort #'< 11 '(5 6 4 7 8 3 2 9 1 10 0))
(0 1 2 3 4 5 6 7 8 9 10)

解答

●問題23

リスト ps がリスト ls の「接頭辞 (prefix) 」か判定する関数 prefix ls ps を定義してください。接頭辞とは、列の先頭からある位置までの部分列のことです。リスト [a, b, c, d] の接頭辞は [ ], [a], [a, b], [a, b, c], [a, b, c, d] の 5 つになります。

> (prefix '(a b c d e f) '(a b c))
t
> (prefix '(a b c d e f) '(a b c e))
nil
> (prefix '(a b c d e f) '())
t

解答

●問題24

リスト ss がリスト ls の「接尾辞 (suffix) 」か判定する関数 suffix ls ss を定義してください。接尾辞とは、列のある位置から末尾までの部分列のことです。リスト [a, b, c, d] の接尾辞は [a, b, c, d], [b, c, d], [c, d], [d], [ ] の 5 つになります。

> (suffix '(a b c d e f) '(d e f))
t
> (suffix '(a b c d e f) '())
t
> (suffix '(a b c d e f) '(f g))
nil

解答

●問題25

リスト xs がリスト ls の部分リストか判定する関数 sublistp xs ls を定義してください。なお、Common Lisp には同等の機能を持つ関数 subsetp があります。

> (sublistp '(c d e) '(a b c d e f))
t
> (sublistp '(d e) '(a b c d e f))
t
> (sublistp '(d e g) '(a b c d e f))
nil
> (sublistp '() '(a b c d e f))
t

解答


●解答1

リスト : 要素がただひとつか

(defun singlep (ls)
  (and (consp ls) (null (cdr ls))))

Common Lisp の場合、引数 ls がリストで、かつ (cdr ls) が空リストであれば、そのリストの要素は一つしかないことがわかります。length でリストの長さを求める必要はありません。

●解答2

リスト : 要素が二つあるか

(defun doublep (ls)
  (and (consp ls) (singlep (cdr ls))))

Common Lisp の場合、述語 consp が真であればリストに一つ以上の要素があることがわかります。あとは (cdr ls) が一つしか要素がないことを述語 singlep で確認します。length でリストの長さを求める必要はありません。

●解答3

リスト : リスト xs は ys よりも長いか

(defun longerp (xs ys)
  (cond ((null xs) nil)
        ((null ys) t)
        (t (longerp (cdr xs) (cdr ys)))))

; 別解
(defun longerp1 (xs ys)
  (and (consp xs)
       (or (null ys)
           (longerp1 (cdr xs) (cdr ys)))))

リストの先頭から順番にたどり、途中で ys が空リストになれば ys の方が長いことがわかります。length でリストの長さを求めて比較するよりも、このプログラムの方が効率的だと思います。

●解答4

リスト :  リストの最後尾を求める

(defun my-last (ls)
  (if (null (cdr ls))
      ls
    (my-last (cdr ls))))

; 別解
(defun my-last1 (ls)
  (do ((ls ls (cdr ls)))
      ((null (cdr ls)) ls)))

関数 my-last は単純な再帰定義で、my-last1 は繰り返し do を使ってリストの最後尾を求めています。

リスト : 最後尾の要素を取り除く

(defun my-butlast (ls)
  (if (null (cdr ls))
      nil
    (cons (car ls) (my-butlast (cdr ls)))))

; 別解
(defun my-butlast1 (ls)
  (do ((ls ls (cdr ls))
       (a nil))
      ((null (cdr ls)) (nreverse a))
    (push (car ls) a)))

my-butlast は引数のリスト ls の要素が一つになるまで再帰呼び出しをします。要素が一つになったら空リストを返します。あとは、再帰呼び出しからの返り値に cons で要素を追加していくだけです。

別解は do を使った繰り返しバージョンです。累積変数 a に要素を格納し、引数 ls の要素がひとつになったならば、nreverse でリスト a を破壊的に反転して返します。reverse を使うよりも効率的です。

●解答5

リスト : リストの先頭から n 個の要素を取り出す

(defun take (ls n)
  (if (or (<= n 0) (null ls))
      nil
    (cons (car ls) (take (cdr ls) (1- n)))))

; 別解
(defun take1 (ls n)
  (do ((n n (1- n))
       (ls ls (cdr ls))
       (a nil))
      ((or (<= n 0) (null ls)) (nreverse a))
    (push (car ls) a)))

引数 n が 0 以下または引数 ls が空リストの場合は空リストを返します。そうでなければ take を再帰呼び出しして、その返り値にリストの先頭要素 (car ls) を追加します。別解は do による繰り返しバージョンです。累積変数 a に要素を格納して、n が 0 以下または ls が空リストになったら、nreverse でリスト a を破壊的に反転して返します。

●解答6

リスト : リストの先頭から n 個の要素を削除する

(defun drop (ls n)
  (if (or (<= n 0) (null ls))
      ls
    (drop (cdr ls) (1- n))))

; 別解
(defun drop1 (ls n)
  (do ((n n (1- n))
       (ls ls (cdr ls)))
      ((or (<= n 0) (null ls)) ls)))

drop は簡単です。引数 n が 0 以下または引数 ls が空リストになるまで drop を再帰呼び出しするだけです。別解は do による繰り返しバージョンです。

●解答7

リスト : 部分リストを取り出す

(defun my-subseq (ls s e)
  (take (drop ls s) (- e s)))

my-subseq は drop と take を使うと簡単です。drop で ls から s 個の要素を取り除き、そのリストから e - s 個の要素を take で取り出すだけです。

●解答8

リスト : リストの末尾から n 個の要素を取り除く

(defun my-butlast2 (ls &optional (n 1))
  (take ls (- (length ls) n)))

リスト ls の長さを m とすると、リストの末尾から n 個の要素を取り除くことは、リストの先頭から m - n 個の要素を取り出すことと同じになります。取り出す要素の個数を (- (length ls) n)) で求めて take で要素を取り出します。

●解答9

リスト : リストの分割

(defun group (ls n)
  (if (null ls)
      nil
    (cons (take ls n) (group (drop ls n) n))))

; 別解
(defun group1 (ls n)
  (do ((ls ls (drop ls n))
       (a nil))
      ((null ls) (nreverse a))
    (push (take ls n) a)))

関数 group は take と drop を使うと簡単に定義できます。ls が空リストの場合は分割できないので空リストを返します。これが再帰の停止条件になります。ls が空リストでない場合、まず take で n 個の要素を格納したリストを求めます。次に、n 個の要素を取り除いたリストを drop で求めて group を再帰呼び出しします。その返り値に take で取り出したリストを cons で追加すればいいわけです。

別解は do による繰り返しバージョンです。take で取り出したリストを累積変数 a に格納し、ls が空リストになったら nreverse でリスト a を破壊的に反転して返します。

●解答10

リスト : 要素の位置を求める

(defun my-position (x ls)
  (labels ((position-sub (ls n)
             (cond ((null ls) nil)
                   ((eql x (car ls)) n)
                   (t (position-sub (cdr ls) (1+ n))))))
    (position-sub ls 0)))

; 別解
(defun my-position1 (x ls)
  (do ((n 0 (1+ n))
       (ls ls (cdr ls)))
      ((null ls) nil)
    (when (eql x (car ls))
      (return n))))

my-position は局所関数 position-sub を呼び出します。position-sub の引数 n が位置を表します。ls が空リストの場合は nil を返します。x と等しい要素を見つけた場合は n を返します。そうでなければ、position-sub を再帰呼び出しして次の要素を調べます。別解は do を使った繰り返しバージョンです。

●解答11

リスト : 要素の個数を求める

(defun my-count (x ls)
  (cond ((null ls) 0)
        ((eql (car ls) x)
         (1+ (my-count x (cdr ls))))
        (t (my-count x (cdr ls)))))

; 別解
(defun my-count0 (x ls)
  (labels ((count-sub (ls a)
             (cond ((null ls) a)
                   ((eql (car ls) x)
                    (count-sub (cdr ls) (1+ a)))
                   (t (count-sub (cdr ls) a)))))
    (count-sub ls 0)))

(defun my-count1 (x ls)
  (do ((c 0)
       (ls ls (cdr ls)))
      ((null ls) c)
    (when (eql (car ls) x)
      (incf c))))

(defun my-count2 (x ls)
  (reduce #'(lambda (a y) (if (eql x y) (1+ a) a)) ls :initial-value 0))

my-count は再帰呼び出しで x と等しい要素を数えます。my-count0 は my-count の末尾再帰バージョン、my-count1 は do による繰り返しバージョン、my-count2 は reduce を使ったバージョンです。

●解答12

リスト : 要素の合計値を求める

(defun sum-list (ls)
  (apply #'+ ls))

(defun sum-list0 (ls)
  (reduce #'+ ls))

(defun sum-list1 (ls)
  (if (null ls)
      0
    (+ (car ls) (sum-list1 (cdr ls)))))

(defun sum-list2 (ls)
  (labels ((sum-list-sub (ls a)
             (if (null ls)
                 a
               (sum-list-sub (cdr ls) (+ (car ls) a)))))
    (sum-list-sub ls 0)))

(defun sum-list3 (ls)
  (do ((sum 0)
       (ls ls (cdr ls)))
      ((null ls) sum)
    (incf sum (car ls))))

Common Lisp の場合、一番簡単なプログラムは apply または reduce を使う方法でしょう。sum-list1 は再帰定義でリストの和を求めています。これを末尾再帰にしたものが sum-list2 です。sum-list3 は do を使った繰り返しバージョンです。

●解答13

リスト : リストから最大値と最小値を求める

; 最大値を求める
(defun max-list (ls) (apply #'max ls))

(defun max-list0 (ls) (reduce #'max ls))

(defun max-list1 (ls)
  (labels ((max-list-sub (ls m)
             (cond ((null ls) m)
                   ((< m (car ls))
                    (max-list-sub (cdr ls) (car ls)))
                   (t
                    (max-list-sub (cdr ls) m)))))
    (max-list-sub (cdr ls) (car ls))))

(defun max-list2 (ls)
  (do ((ls (cdr ls) (cdr ls))
       (m (car ls)))
      ((null ls) m)
    (when (< m (car ls))
      (setf m (car ls)))))

(defun max-list3 (ls)
  (let ((m (car ls)))
    (dolist (x (cdr ls) m)
      (when (< m x) (setf m x)))))

; 最小値を求める
(defun min-list (ls) (apply #'min ls))

(defun min-list0 (ls) (reduce #'min ls))

(defun min-list1 (ls)
  (labels ((min-list-sub (ls m)
             (cond ((null ls) m)
                   ((> m (car ls))
                    (min-list-sub (cdr ls) (car ls)))
                   (t
                    (min-list-sub (cdr ls) m)))))
    (min-list-sub (cdr ls) (car ls))))

(defun min-list2 (ls)
  (do ((ls (cdr ls) (cdr ls))
       (m (car ls)))
      ((null ls) m)
    (when (> m (car ls))
      (setf m (car ls)))))

(defun min-list3 (ls)
  (let ((m (car ls)))
    (dolist (x (cdr ls) m)
      (when (> m x) (setf m x)))))

max-list と min-list は apply を使って関数 max, min を呼び出します。reduce を使っても簡単にプログラムすることができます。max-list1 と min-list1 は末尾再帰でプログラムしています。あとのプログラムは do と dolist を使った繰り返しバージョンです。

●解答14

リスト : x と y は隣り合っているか

(defun adjacent (x y ls)
  (unless (null (cdr ls))
    (if (and (eql (car ls) x) (eql (cadr ls) y))
        t
      (adjacent x y (cdr ls)))))

; 別解
(defun adjacent1 (x y ls)
  (do ((ls ls (cdr ls)))
      ((null (cdr ls)))
    (when (and (eql (car ls) x) (eql (cadr ls) y))
      (return t))))

最初にリストの要素が二つ以上あることをチェックします。次に、先頭の要素が x と等しくて、次の要素が y と等しいことをチェックします。そうであれば t を返し、そうでなければ adjacent を再帰呼び出しして、次の要素を調べます。別解は do を使った繰り返しバージョンです。

●解答15

リスト : x は y よりも前に出現しているか

(defun before (x y ls)
  (let ((xs (member x ls)))
    (and xs (member y (cdr xs)))))

関数 before は関数 member を使うと簡単にプログラムすることができます。ls から x を member で探します。x を見つけた場合、xs の先頭要素は x になります。それを取り除いたリストから member で y を探せばいいわけです。

●解答16

リスト : 数列の生成

(defun iota (n m)
  (if (> n m)
      nil
    (cons n (iota (1+ n) m))))

; 別解
(defun iota1 (n m)
  (do ((m m (1- m))
       (a nil))
      ((< m n) a)
    (push m a)))

(defun iota2 (n m)
  (labels ((iota-sub (m a)
             (if (< m n)
                 a
               (iota-sub (1- m) (cons m a)))))
    (iota-sub m nil)))

関数 iota は簡単です。n が m より大きい場合は空リストになります。n が m 以下の場合、iota を再帰呼び出しして n + 1 から m までのリストを生成し、その先頭に n を追加するだけです。別解は do を使った繰り返しバージョンと末尾再帰バージョンです。この場合、後ろから数値を生成していることに注意してください。m が n よりも小さくなったならばリスト a を返します。

●解答17

リスト : 集合の生成

(defun set-of-list (ls)
  (cond ((null ls) nil)
        ((member (car ls) (cdr ls))
         (set-of-list (cdr ls)))
        (t
         (cons (car ls) (set-of-list (cdr ls))))))

; 別解
(defun set-of-list1 (ls)
  (do ((ls ls (cdr ls))
       (a nil))
      ((null ls) (nreverse a))
    (pushnew (car ls) a)))

(defun set-of-list2 (ls)
  (reduce #'(lambda (a x) (if (member x a) a (cons x a)))
          ls
          :initial-value nil))

述語 set-of-list はリストから重複要素を取り除きます。空リストは重複要素がないので空リストのままです。次の節で、リストの先頭要素 (car ls) が残りのリスト (cdr ls) にあるか memv で調べ、同じ要素があれば集合に加えません。else 節で同じ要素がない場合はそれを集合に加えます。別解は do を使った繰り返しバージョンと reduce を使ったバージョンです。

●解答18

リスト : 集合の和

(defun my-union (xs ys)
  (cond ((null xs) ys)
        ((member (car xs) ys)
         (my-union (cdr xs) ys))
        (t
         (cons (car xs) (my-union (cdr xs) ys)))))

; 別解
(defun my-union1 (xs ys)
  (do ((xs xs (cdr xs))
       (a ys))
      ((null xs) a)
    (pushnew (car xs) a)))

(defun my-union2 (xs ys)
  (reduce #'(lambda (a x) (if (member x ys) a (cons x a)))
          xs
          :initial-value ys))

最初の節は空集合 (空リスト) と集合 ys の和は ys であることを表しています。次の節で、要素 (car xs) が集合 ys に含まれていれば、それを新しい集合に加えません。(car xs) が ys に含まれていなければ、それを集合に追加します。別解は do と reduce を使ったバージョンです。

●解答19

リスト : 集合の積

(defun my-intersection (xs ys)
  (cond ((null xs) nil)
        ((member (car xs) ys)
         (cons (car xs) (my-intersection (cdr xs) ys)))
        (t (my-intersection (cdr xs) ys))))

; 別解 1
(defun my-intersection1 (xs ys)
  (do ((xs xs (cdr xs))
       (a nil))
      ((null xs) a)
    (when (member (car xs) ys)
      (push (car xs) a))))

(defun my-intersection2 (xs ys)
  (reduce #'(lambda (a x) (if (member x ys) (cons x a) a))
          xs
          :initial-value nil))

最初の節は空集合 (空リスト) と集合 ys の積は空集合であることを表しています。次の節で、要素 (car xs) が集合 ys に含まれていれば、それを新しい集合に追加します。そうでなければ、最後の節で要素 (car xs) を集合に追加しません。別解は do と reduce を使ったバージョンです。

●解答20

リスト : 集合の差

(defun difference (xs ys)
  (cond ((null xs) nil)
        ((member (car xs) ys)
         (difference (cdr xs) ys))
        (t
         (cons (car xs) (difference (cdr xs) ys)))))

; 別解
(defun difference1 (xs ys)
  (do ((xs xs (cdr xs))
       (a nil))
      ((null xs) a)
    (unless (member (car xs) ys)
      (push (car xs) a))))

(defun difference2 (xs ys)
  (reduce #'(lambda (a x) (if (member x ys) a (cons x a)))
          xs
          :initial-value nil))

最初の節は、空集合と集合 ys の差は空集合であることを表しています。次の節で、要素 (car xs) が ys に含まれいる場合は集合にそれを追加しません。そうでなければ、最後の節で要素 (car xs) を集合に追加します。別解は do と reduce を使ったバージョンです。

●解答21

リスト : リストのマージ

(defun merge-list (pred xs ys)
  (cond ((null xs) ys)
        ((null ys) xs)
        ((funcall pred (car xs) (car ys))
         (cons (car xs) (merge-list pred (cdr xs) ys)))
        (t (cons (car ys) (merge-list pred xs (cdr ys))))))

最初の節は、空リストとリスト ys をマージすると ys になることを表しています。次の節は、リスト xs と空リストをマージすると xs になることを表しています。この 2 つの節が、再帰呼び出しの停止条件になります。

3 番目の節で、それぞれのリストの先頭要素を述語 pred で比較し、pred が真を返す場合は (car xs) をマージしたリストの先頭に追加し、そうでなければ最後の節で (car ys) をマージしたリストの先頭に追加します。merge-list を再帰呼び出しするときは、xs または ys の先頭要素を取り除いて呼び出すことに注意してください。

●解答22

マージソートはリストの長さを 1, 2, 4, 8, ... と増やしていくよりも、再帰的に考えた方が簡単です。まず、ソートするリストを 2 つに分割して、前半部分をソートします。次に、後半部分をソートして、その結果をマージすればいいわけです。

再帰呼び出しするたびにリストは 2 つに分割されるので、最後にリストの要素はひとつとなります。これはソート済みのリストなので、ここで再帰呼び出しを終了してマージ処理を行えばいいわけです。プログラムは次のようになります。

リスト : マージソート

(defun merge-sort (pred n ls)
  (if (= n 1)
      (list (car ls))
    (let ((m (floor n 2)))
      (merge-list pred
                  (merge-sort pred m ls)
                  (merge-sort pred (- n m) (drop ls m))))))

関数 merge-sort の引数 pred が要素を比較する述語、引数 ls がソートするリスト、引数 n がリストの長さを表します。merge-sort はリストを分割する処理で、新しいリストを作らないことに注意してください。merge-sort はソートするリストの範囲を開始位置と長さで表しています。リストを二分割する場合、前半部分は ls と m (= n / 2) で表し、後半部分を (drop ls m) と (- n m) で表します。

あとは merge-sort を再帰呼び出しでリストを分割していき、リストの長さが 1 になったならば新しいリストを返します。そして、merge-sort でソートしたリストを merge-list でマージすればいいわけです。

●解答23

リスト : 接頭辞の判定

(defun prefix (ls ps)
  (cond ((null ps) t)
        ((eql (car ls) (car ps))
         (prefix (cdr ls) (cdr ps)))
        (t nil)))

接頭辞の判定は簡単です。最初の節は、空リストは接頭辞であることを表しています。次の節で、リストの先頭要素が等しい場合は、残りのリスト (cdr ps) が (cdr ls) の接頭辞であることを確かめます。

●解答24

リスト : 接尾辞の判定

(defun suffix (ls ss)
  (prefix (drop ls (- (length ls) (length ss))) ss))

接尾辞の判定も簡単です。リスト ls と ss の長さの差分を求め、ls の先頭から差分の個数だけ要素を取り除きます。これで ls と ss の長さが等しくなるので、あとは prefix で比較するだけです。

●解答25

リスト : 部分リストの判定

(defun sublist (ks ls)
  (cond ((null ls) nil)
        ((prefix ls ks) t)
        (t (sublist ks (cdr ls)))))

sublist は prefix を使うと簡単です。最初の節で ls が空リストの場合、ks は部分リストではないので nil を返します。次の節で、ks が ls の接頭辞であれば部分リストなので t を返します。それ以外の場合は ls の先頭要素を取り除いて、sublist を再帰呼び出しするだけです。


Copyright (C) 2010 Makoto Hiroi
All rights reserved.

[ PrevPage | Common Lisp | NextPage ]