cl-slice: シーケンス・配列の部分取得

概要

このページでは、リストや配列などの一部を取得するライブラリであるcl-sliceについて説明します。

cl-sliceは拡張可能に設計されていますが、拡張の仕方は説明しません。また、cl-sliceは配列を扱う際には非常に汎用性が高く利用できますので、sliceシンボルはインポートして使います。

このページでは以下の形式で準備されていることを前提に説明します。
(asdf:load-system :cl-slice)
; => T

(shadowing-import 'cl-slice:slice)
; => T

なお、cl-sliceは2017年の10月にオリジナルの作者であるTamas K Pappによるメンテナンスが終了しましたが、alexandria, anaphora, let-plusという代表的な3つのライブラリにしか依存していないため、特に問題なく使用することができます。作者自身が後継のメンテナを募集中ということですので、興味があればこちらの記事(英語)を参照してください。

整数:対応する要素の取得

cl-sliceはslice関数でリストや配列を操作します。最も基本的なのは、整数値で添字(インデックス)を指定し、対応する要素を取得することです。
(slice #(1 2 3 4 5) 1)
; => 2

これがANSI Common Lisp標準のnth(リスト)、aref(配列)、svref(シンプルベクトル)という汎参照(アクセッサ)と異なるのは、逆順でも指定できる点です。-1から始まる負値を指定すると、逆順から見た要素番号に対応する要素を取得します。
(slice #(1 2 3 4 5) -2)
; => 4

また、slicedefunで定義される一般の関数ではなく、defgenericdefmethodで定義される総称関数です。そのため、リストでも配列でも区別することなくアクセスできます。(総称関数はCommon Lisp Object System(CLOS)の一部で、ANSIの第7章で標準化されています。標準仕様入門の第7章「オブジェクト」を参照してください。)
(slice '(1 2 3 4 5) -2)
; => (4)

多次元配列の場合は要素番号を可変長引数のように列挙するだけです。
(slice #2a((1 2)(3 4)) 1 0)
; => 3

t:全ての要素の取得

前節の指定方法において、tシンボルを使うと、対応する次元の全ての要素を取得できます。これは、2次元配列(行列)の行のみ、列のみを取得するのに便利です。

特定の行のみを抽出するには、以下のようにします。
(slice #2a((1 2)(3 4)) 1 t)
; => #(3 4)

特定の列のみを抽出するには、以下のようにします。
(slice #2a((1 2)(3 4)) t 1)
; => #(2 4)

コンス:範囲を指定した取得

要素番号を整数で特定するのではなく、範囲で指定する場合はコンスを使用します。
(slice #(1 2 3 4 5) '(1 . 4))
; => #(2 3 4)

この場合、コンスで指定する数字は対応する要素番号の手前の区切りを意味します。つまり、1の場合は2番目の要素の手前を、4の場合は5番目の要素の手前を意味しますので、2番目から4番目までの要素を取得することになります。

なお、終端指定でnilを指定すると最後の要素まで取得されます。
(slice #(1 2 3 4 5) '(1 . nil))
; => #(2 3 4 5)

このコンスによる指定でも負の値を使うことができます。例えば、最初と最後の要素を除く範囲を指定したい場合は、以下のようになります。
(slice #(1 2 3 4 5) '(1 . -1))
; => #(2 3 4)

ただし、範囲の指定はあくまでも始点から終点までですので、同一の番号を指定することはできません。また、逆順にすることもできません。

範囲の指定は多次元配列でも使用できますし、整数やtの指定と混ぜて使うこともできます。
(slice #2a((1 2 3)(4 5 6)(7 8 9)) t '(1 . nil))
; => #2A((2 3) (5 6) (8 9))

ベクトル:要素の個別取得

指定方法がベクトルの場合、対応する要素を個別に取得することができます。
(slice #(10 20 30 40 50 60 70 80 90) #(2 3 5 7))
; => #(30 40 60 80)

ここでももちろんコンスを用いた指定を組み合わせることができます。
(slice #(10 20 30 40 50 60 70 80 90) #((1 . 4) (-3 . nil)))
; => #(20 30 40 70 80 90)

ビット型のベクトル:フラグとしての要素取得

少し変わり種としての利用法ですが、ベクトルがbit型の場合(つまりbit-vectorの場合)、フラグとして使用することができます。
(slice #(10 20 30 40 50 60 70 80 90) #*010101010)
; => #(20 40 60 80)

その他の機能

以上がcl-sliceのメインですが、他にも少しだけ機能を紹介します。

headtail

言葉の通りですが、headで先頭から、tailで終端から指定することができます。この2つのシンボルは関数ですので、使用する場合はインポートするかパッケージ名も付けて使用する必要があります。
(slice #(1 2 3 4 5) (cl-slice:head 2))
; => #(1 2)

(slice #(1 2 3 4 5) (cl-slice:tail 2))
; => #(4 5)

maskwhich

cl-sliceパッケージに含まれるslice以外の関数としてmaskwhichを紹介します。

mask総称関数は述語関数とシーケンスを引数に取り、述語関数がtを返す部分のフラグを立てたビットマスクを返します。
(cl-slice:mask #'evenp #(1 2 3 4 5))
; => #*01010

また、which総称関数も述語関数とシーケンスを引数に取り、述語関数がtを返す部分の要素番号をベクトルにして返します。
(cl-slice:which #'evenp #(11 22 33 44 55))
; => #(1 3)

この例の場合、2244evenp(偶数かどうか)で真になるため、22の要素番号である144の要素番号である3をベクトルにして返します。

ANSI Common Lispにもposition-ifという同様の関数がありますが、こちらは条件を満たす1つの要素しか取得できません。条件に合致する複数の要素を取得したい場合は、maskwhichなどを利用すると大変便利なため、データのフィルタリングなどを行う場合は積極的に活用してください。

0 件のコメント :

コメントを投稿