独学Common Lisp

Array & Vector Dictionary

概要

Common Lispの配列は非常に柔軟ですが、柔軟な仕組みを使うには少し複雑さが伴います。

このページでは、配列及びベクタの仕様についてメモしていきます。

汎用シンプルベクタ

make-arrayで最も単純に作ることができるのはsimple-vectorです。

(make-array 5)
; => #(0 0 0 0 0)

(typep (make-array 5) 'simple-vector)
; => T

simple-vectorは以下の特徴を持つオブジェクトです。

以下にサンプルを示します。

(typep (make-array 5) 'array)
; => T

(typep (make-array 5) 'vector)
; => T

(array-has-fill-pointer-p (make-array 5))
; => NIL

(svref (make-array 5) 0)
; => 0

(array-element-type (make-array 5))
; => T

一番最後のTはすべての型の根源としてのTです。

declareによる型の指定ではsimple-vectorを使うことができます。

(declare (type simple-vector <var>))

これは、(simple-array t)と等価です。

(declare (type (simple-array t) <var>))

型指定付非可変長ベクタ

配列を作る際に:element-typeを指定すると配列要素の型を指定できます。

(array-element-type (make-array 5 :element-type 'double-float))
; => DOUBLE-FLOAT

(let ((v (make-array 5 :element-type 'double-float)))
  (setf (aref v 0) 1))
; => ERROR

(svref (make-array 5 :element-type 'double-float) 0)
; => ERROR

型を指定すると当然のことながら異なる型をセットすることはできません。これは例えば'double-floatで指定すると、1もセットできなくなります。1'double-float的表現は1d0です。

また、型を指定するとsvrefでアクセスできなくなります。simple-vectorはCommon Lisp HyperSpecによれば'general simple vector'という記述にある通り、型の指定がないことが条件です。

declareによる型の指定は以下の通りsimple-vectorではありません。

(declare (type (simple-array double-float) <var>))

配列要素の型を指定してもアクセス速度はsimple-vectorとあまり変わらないようです。メモリの使用量が変わるのかもしれません。

fill-pointer付ベクタ

配列作成時に:fill-pointerを指定すると、配列要素の範囲内であたかもそこが終端であるかのように振る舞う配列を作ることができます。

(make-array 5 :fill-pointer 2)
; => #(0 0)

(length (make-array 5 :fill-pointer 2))
; => 2

このfill-pointerを使うと、vector-pushでベクタに要素を容易に追加することができます。

(let ((v (make-array 3 :fill-pointer 0)))
  (vector-push 1 v)
  (vector-push 2 v)
  (vector-push 3 v)
  v)
; => #(1 2 3)

ただし、あらかじめ用意した配列の要素数を超える要素をpushすることはできません。

(let ((v (make-array 3 :fill-pointer 0)))
  (vector-push 1 v)
  (vector-push 2 v)
  (vector-push 3 v)
  (vector-push 4 v)
  v)
; => #(1 2 3)

エラーにもならないようなので注意が必要です。

また、fill-pointerを付けるとsimple-arrayでもsimple-vectorでもなくなります。

(typep (make-array 5 :fill-pointer 0) 'simple-array)
; => NIL

(typep (make-array 5 :fill-pointer 0) 'simple-vector)
; => NIL

型の指定はarrayを使うしかないでしょう。

(declare (type (array t) <var>))

おそらくfill-pointerが単独で用いられることは少ないのではないかと思います。

可変長ベクタ

fill-pointerの力を発揮するのはadjustableと組み合わさった時です。二つのオプションを同時に使うと後入先出法によるスタックを簡単に実装できます。

adjustableと組み合わさると可変長ベクタになるので初期に用意すべき要素数は0で構いません。ただし、配列の要素を伸ばしながら追加するにはvector-push-extendを使います。

(let ((v (make-array 0 :fill-pointer 0 :adjustable t)))
  (vector-push-extend 1 v)
  (vector-push-extend 2 v)
  (vector-push-extend 3 v)
  v)
; => #(1 2 3)

なお、pushがあるのでpopもあります。

(let ((v (make-array 0 :fill-pointer 0 :adjustable t)))
  (vector-push-extend 1 v)
  (vector-push-extend 2 v)
  (vector-push-extend 3 v)
  (vector-pop v)
  v)
; => #(1 2)

文字列ストリーム型可変長ベクタ

このような可変長ベクタは文字列出力用のバッファとして利用できます。

(let ((buf (make-array 0
                       :element-type 'base-char
                       :fill-pointer 0
                       :adjustable t))
      (name "CL"))
  (with-output-to-string (out buf)
    (format out "My name is ~a.~%" name)
    (format out "Hello, ~a!!~%" name))
  (princ buf))

My name is CL.
Hello, CL!!
; => "My name is CL.
;    Hello, CL!!
;    "

with-output-to-stringはバッファを用意せずに使うこともできますが、バッファとしての変数に書き出すためには必ずfill-pointerが必要です。


Copyright © 2017- satoshiweb.net All rights reserved.