プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(47)

今日も「実践Common Lisp」をやります。先日に引き続きベクタの話です。

特殊ベクタ

ここまで見てきたベクタは、どういった型のオブジェクトでも要素とできました。
特殊ベクタとは、ある型の要素だけを保持するように制限したものだそうです。
わざわざそれを使うメリットとしては、コンパクトにデータを保存できるようになるかもしれないこと、
アクセスが早くなるかもしれないことが挙げられるようです。
微妙な言い方なので、そんな目に見えて変わるものでないのかもしれません。
それよりもそれ事態が重要なデータ型であるような特殊ベクタがあるそうです。

まず、文字だけを保持する特殊ベクタ、つまり文字列。なるほど。
ダブルクォートで括る独自の読み書き、印字のシンタックス、専用の関数がありますが、
結局はベクタの一種なのでベクタにできることは文字列にもできるそうです。

"foo"のような文字列リテラルは、#()シンタックスによるリテラルに似たもので、変更はできないと。
サイズの変更ができる文字列を作るなら、make-arrayに:element-typeを指定するそうです。
:element-typeは、見た通りに型を指定するためのものだそう。
文字列を作るためというよりかは、特殊ベクタ全般のためのような気がしますね。

CL-USER> (make-array 5 :fill-pointer 0 :adjustable t :element-type 'character)
""
CL-USER> (defparameter *str* 
           (make-array 5 :fill-pointer 0 :adjustable t :element-type 'character))
*STR*
CL-USER> (defun push-string (str target)
           (let ((x (concatenate 'list str)))
             (labels ((push-list (y)
                        (if (eql y nil)
                            target
                            (progn (vector-push-extend (car y) target)
                                   (push-list (cdr y))))))
               (push-list x))))
PUSH-STRING
CL-USER> CL-USER> (defun push-string (str target)
           (let ((x (concatenate 'list str)))
             (labels ((push-list (y)
                        (if (eql y nil)
                            target
                            (progn (vector-push-extend (car y) target)
                                   (push-list (cdr y))))))
               (push-list x))))
PUSH-STRING
CL-USER> (push-string "string" *str*)
"string"
CL-USER>

探せばありそうな文字列をベクタに突っこむ関数をせっかくなので自作して試しました。
久しぶりに再帰を書きたかったのです。なるほどなるほど。

文字列以外の特殊ベクタとしては、ビットベクタというものがあるそうです。
その名の通り、0あるいは1のみを要素として持つということ。
印字のシンタックスは、#*0101といった感じのようです。
2つのビット配列の論理積をとるとかの関数がたくさん用意されているらしいです。
make-arrayで作るときに:element-typeに渡すのは、シンボルbitです。

シーケンスとしてのベクタ

ベクタとリストはシーケンスという型のサブタイプである、ということで
ここから紹介されるシーケンス関数は、ベクタにもリストにも適用できるようです。

基本的なシーケンス関数としては、lengthとeltがあるそうです。
lengthはシーケンスを一つ引数にとり、引数に含まれる要素の数を返します。
eltはelementの略で、引数にシーケンスとインデックスとしての整数をとり、
シーケンスの要素のうちインデックスに対応する要素を返してくれます。

CL-USER> *vec*
#(A B D E F H)
CL-USER> (elt *vec* 0)
A
CL-USER> (elt *vec* 3)
E
CL-USER> (elt *vec* 6)
; Evaluation aborted on #<SB-KERNEL:INDEX-TOO-LARGE-ERROR expected-type: (INTEGER 0 5) datum: 6>.
CL-USER> (setf (elt *vec* 0) 'z)
Z
CL-USER> *vec*
#(Z B D E F H)
CL-USER> (elt '(a b c) 1)
B
CL-USER>

ふむふむ。setfも可能です。最後は本当にリストでできるのか確認したくなりました。

シーケンス反復関数

lengthとeltさえあれば、理論上シーケンスへの全ての操作は可能だそうです。
でもだからって他の関数が無かったらやばいですよね。というわけで豊富なライブラリがあるそう。
いくつか例として関数が紹介されます。

CL-USER> *vec*
#(Z B D E F H)
CL-USER> *str*
"string"
CL-USER> (count #\s *str*)              ;シーケンスの中の要素の数を調べる
1
CL-USER> (find #\i *str*)               ;要素の検索、要素かnilを返す
#\i
CL-USER> (find #\z *str*)
NIL
CL-USER> (position 'b *vec*)            ;要素のインデックスか、見つからなければnilを返す
1
CL-USER> (position 'a *vec*)
NIL
CL-USER> (remove 'h *vec*)              ;引数と一致した要素を取り除いたシーケンスを返す
#(Z B D E F)
CL-USER> (remove 'a *vec*)              ;破壊的関数じゃなかった...
#(Z B D E F H)
CL-USER> (substitute 'a 'z *vec*)       ;要素の置き換え
#(A B D E F H)
CL-USER> *vec*                          ;やっぱり非破壊的
#(Z B D E F H)
CL-USER> 

これらの関数もキーワード引数で振舞いを変更できるそう。
例えば、どの関数もシーケンス中の要素と引数の型が同じであることが期待されていますが、
変更する方法も2つあるそうです。
1つ目は、真偽値を比較する関数はデフォルトがeqlなので、:testキーワードでそれを変更すること。
2つ目は、:keyキーワードで1引数関数とやらを渡す方法だそうです。
使ったことはあるけど、言葉にされると全然よくわかりませんなあ。困った。
とにかく。この1引数関数がシーケンス中の各要素に対して呼び出されキーの値になり、
その値がアイテムとの比較に使われるというわけですね。

CL-USER> (find 'a '((a b) (b a) (c b)))
NIL
CL-USER> (find 'a '((a b) (b a) (c b)) :key #'car)
(A B)
CL-USER>

関数の効力をシーケンスの特定の部分に限定するときは、:startや:endによる指定を行なえばいいそう。
また、:from-endにnil以外の値を与えればシーケンスを逆側から走査しますが、
findとposition以外の関数の結果には直接影響しないということです。

CL-USER> (remove 'a '#(a b c a d e f a) :start 2 :end 6)
#(A B C D E F A)
CL-USER> (find 'neko '#((neko tama) (inu hiroshi) (neko el)) :key #'car :from-end t)
(NEKO EL)
CL-USER>

ただし、取り除いたり置き換えたりする要素の個数を指定する:countキーワードと:from-endを組み合わせると
当然removeやsubstituteの結果は通常と異なるものになりがちです。
それと、:from-endは:keyや:testで指定した関数が副作用を含んでいる場合には注意すべきだそうです。

なかなか使いでのあることがいっぱい出てきてなかなか楽しいです。
でも疲れたので寝ます。おやすみなさい。