【Common Lisp】シーケンス関数の基本
ジェネリックプログラミングの一環として、シーケンス関数について勉強したので、そのメモです。
目標
- ジェネリックとは何かざっくりと理解する
- 基本的なシーケンス関数を使えるようにする
ジェネリックである、とは?
簡単に言えば、異なるものを同じ方法で扱えること、になると思います。 lispは(他の多くの言語でもそうだと思いますが)いくつもの型を持っています。そのため同じような処理を行う時には、異なる型のオブジェクトを同じ方法で扱うことができると便利です。
シーケンス関数
データをジェネリックに扱うために用意されている手法の1つが、シーケンス関数です。シーケンスとは、複数の要素が並んで列になっているようなデータ構造のことです。シーケンスであるリスト、配列、文字列を同一の方法で扱うことができる関数をシーケンス関数と呼びます。
重要なシーケンス関数
基本的なシーケンス関数をいくつか載せておきます。 例に用いる配列を毎回用意するのが面倒なので、トップレベル変数として次の配列を用意しておきました。
CL-USER> (defparameter *arr* (make-array 4 :initial-contents '(2 3 4 5))) *ARR* CL-USER> *arr* #(2 3 4 5)
length
シーケンスの要素数を返します。
CL-USER> (length '(a b c)) 3 CL-USER> (length (make-array 3)) 3 CL-USER> (length "abc") 3
find-if
述語を満たす最初の要素を返します。述語とは、真偽値を返す関数です。
CL-USER> (find-if #'oddp *arr*) 3 CL-USER> (find-if #'numberp '(a b c 1 2)) 1 CL-USER> (find-if #'digit-char-p "abc123") #\1
count
指定した要素の数を返します。
CL-USER> (count 2 *arr*) 1 CL-USER> (count 'a '(a b c 1 2 a b c)) 2 CL-USER> (count #\l "Hello, lisp alien.") 4
position
指定に適合する要素のうち、最初のものの位置(インデックス)を返します。
CL-USER> (position 2 *arr*) 0 CL-USER> (position 'b '(a b c 1 2 a b)) 1 CL-USER> (position #\l "Hello, lisp alien.") 2
some, every
someは述語を満たす要素が存在するかどうかを返します。1つでも満たせば真となり、1つも満たさない場合に偽となります。
everyは全ての要素が述語を満たすかどうかを返します。全ての要素が満たせば真となり、1つでも満たさないものがあれば偽となります。
CL-USER> (some #'oddp *arr*) T CL-USER> (every #'oddp *arr*) NIL CL-USER> (some #'numberp '(a b c)) NIL CL-USER> (every #'numberp '(1 2 3)) T CL-USER> (some #'numberp "I am lisp alien") NIL CL-USER> (every #'characterp "I am lisp alien") T
reduce
中間結果と新しい要素を与えられた関数に適用して、次の中間結果を出す。これを繰り返します。 デフォルトでは中間結果の初期値は最初の要素となります。すなわち、まず1番目の要素と2番目の要素を関数に適用し、それで得られた中間結果と3番目の要素を関数に適用し、、、となります。 この説明ではわかりづらいかもしれません。 例を見た方が早いかも。次の例ではリストの全ての数を加算しています。
CL-USER> (reduce #'+ '(1 2 3 4 5)) 15
中間結果の初期値を与えることもできます。次の例はリストに含まれる最大の偶数を求める例です。初期値を0に設定しないとうまくいきません。
CL-USER> (reduce (lambda (best item) (if (and (evenp item) (> item best)) item best)) '(7 4 6 5 2) :initial-value 0) ;; 初期値 6 CL-USER> (reduce (lambda (best item) (if (and (evenp item) (> item best)) item best)) '(7 4 6 5 2)) 7
map
与えられたシーケンスのそれぞれの要素に関数を適用します。返り値となるシーケンスの型を指定します。
CL-USER> (map 'array (lambda (x) (* x 2)) '(1 2 3)) #(2 4 6) CL-USER> (map 'string (lambda (x) (if (eq x #\s) #\S x)) "this is a string") "thiS iS a String" CL-USER> (map 'list (lambda (x) (if (eq x #\s) #\S x)) "this is a string") (#\t #\h #\i #\S #\Space #\i #\S #\Space #\a #\Space #\S #\t #\r #\i #\n #\g)
subseq
シーケンスの始点と終点をインデックスで指定し、部分列を切り出します。始点〜終点-1となります。
CL-USER> (subseq "america" 2 6) "eric" CL-USER> (subseq #(0 1 2 3 4 5) 1 3) #(1 2) CL-USER> (subseq '(0 1 2 3 4 5) 1 3) (1 2)
sort
指定した方法でソートします。ソート方法は比較関数を与えることで指定します。
CL-USER> (sort '(3 2 5 1) #'<) (1 2 3 5) CL-USER> (sort #(3 2 5 1) #'>) #(5 3 2 1)
参考
コンラッド・バルスキ(2013)「Land of Lisp」 川合史郎訳 オライリー・ジャパン