シーケンス - Alexandria

概要

このページでは、Common Lispの汎用ユーティリティであるAlexandriaの「シーケンス」に関するオペレータを紹介します。

sequence-of-length-p関数: シーケンスの長さの判定

Alexandriaのsequence-of-length-p関数は、シーケンスとその長さを引数として指定し、その情報が正しいかどうかを判定する述語関数です。型がシーケンスではない場合はエラーが通知されます。
(alexandria:sequence-of-length-p '(1 2 3) 3)
; => T

rotate関数: シーケンスの要素の順序を変更

車のタイヤを入れ替えることを「ローテーション」と言いますが、シーケンスの要素の順番を入れ替えるのがAlexandriaのrotate関数です。この関数はかなり高機能で、どのように入れ替えるかを指定することができます。
  • オプショナル引数の第2引数を指定しない場合、最後の要素が先頭に配置されます。
  • 第2引数を指定すると、その個数だけ移動されます。
  • 第2引数を負数で指定しすると、入れ替えの方法が逆順になり、先頭の要素が最後に配置されます。
以下でサンプルを示します。
(alexandria:rotate '(a b c d))
; => (D A B C)

(alexandria:rotate '(a b c d) 2)
; => (C D A B)

(alexandria:rotate '(a b c d) -1)
; => (B C D A)

shuffle関数: シーケンスの要素の順序をランダムに配置

Alexandriaのshuffle関数はその中の通りシーケンスの要素をシャッフルします。:start, :end の各キーワード引数で、シャッフルする範囲を指定することも可能です。
(alexandria:shuffle '(a b c d))
; => (D A B C)

random-elt関数: シーケンスの要素をランダムに取得

random-elt関数も名前を見れば分かる通り、シーケンスの要素をランダムに取得するオペレータです。
(alexandria:random-elt '(a b c d))
; => B

removef, deletefマクロ: 要素を削除するモディファイマクロ

ANSI Common Lispではremove, deleteという関数が定められており、共にシーケンスから特定の要素を削除します。両者の違いは引数を破壊的に変更するかどうかで、引数を再利用する場合はremoveを使い、引数を再利用しない場合はdeleteを使います。

Alexandriaはこれらの関数のモディファイマクロを提供します。モディファイマクロは関数の返り値を変数に束縛し直すものです。

以下にサンプルを示します。
(defparameter *list* '(a b c d))
; => *LIST*

(alexandria:removef *list* 'b)
; => (A C D)

*list*
; => (A C D)

emptyp関数: ベクトルにも対応したendp

ANSI Common Lispではendpという関数が定められており、リストを引数にとってnil(空リスト)であるかどうかを判定します。このendpは引数がlist型以外であればtype-errorコンディションを通知します。

Alexandriaのemptyp関数はベクトルにも拡張してシーケンス全般に対応させたendpです。
(alexandria:emptyp #())
; => T

length=関数・コンパイラマクロ: シーケンスの長さの一致の判定

Alexandriaのlength=というオペレータは関数としても、コンパイラマクロとしても定められています。

まず、関数として使用する場合は、複数のシーケンスを引数に並べます。すると全てのシーケンスの長さが等しいかどうかを判定します。
(alexandria:length= '(a b c) '(1 2 3))
; => T

次に、コンパイラマクロとして使用する場合は、第1引数に一致を確認したい長さをあらかじめ指定します。
(alexandria:length= 3 '(a b c) '(1 2 3))
; => T

あらかじめ検証したい長さを取得できる場合はコンパイラマクロとして使用した方が効率的です(私が検証した訳ではありませんが、ドキュメント文字列に書いてあります)。

copy-sequence関数: 型変換機能付のcopy-seq

ANSI Common Lispの標準仕様ではcopy-seqという関数が定められており、リストまたはベクトルの「シーケンス」一般に対してコピー機能を提供します。

Alexandriaのcopy-sequenceはそれを少しだけ拡張するもので、コピー後の型を指定することができます。コピー後の型が引数と同じであれば単にcopy-seqを呼び出し、異なればcoerce関数を呼び出します。

あらかじめ引数の型とコピー後の型の一致・不一致が分かっていればcopy-seqまたはcoerceを直接呼び出した方が効率的ですが、copy-sequeceには判定のコードも含まれているため、型の一致・不一致が分かっていない場合には便利です。

(alexandria:copy-sequence 'vector '(a b c))
; => #(A B C)

first-elt関数: ベクトルにも対応したfirst

ANSI Common Lispではfirstという関数(アクセッサ)が定められており、リストの最初の要素を取得することができます。

Alexandriaのfirst-elt関数はリストに限らすベクトルの最初の要素も取得することができます。また、(setf first-elt)関数も定められているため、setfマクロで使用することもできます。(このような関数を「アクセッサ」と呼びます。)
(defparameter *vec* #(1 2 3))
; => *VEC*

(alexandria:first-elt *vec*)
; => 1

(setf (alexandria:first-elt *vec*) 1.0d0)
; => 1.0d0

*vec*
; => #(1.0d0 2 3)

last-elt関数: ベクトルにも対応したlast

前節のfirst-eltとほぼ同じですが、最初ではなく最後の要素を参照します。

starts-with-subseq関数: シーケンスの先方一致判定

シーケンスの先頭のいくつかの要素が指定した引数と一致するかどうかを判定します。:return-suffixキーワード引数はデフォルトではnilですが、tにすると先頭の一致した要素を取り除いた残りの部分だけが返されます。

返り値は多値で、1つ目の返り値は一致したかどうか、2つ目の返り値は:return-suffixtで指定した場合の残りのシーケンスです。

以下のサンプルで確認してください。

(alexandria:starts-with-subseq #(1 2) #(1 2 3 4 5))
; => T ;
;    NIL

(alexandria:starts-with-subseq #(1 2) #(1 2 3 4 5) :return-suffix t)
; => T ;
;    #(3 4 5)

(alexandria:starts-with-subseq #(1 3) #(1 2 3 4 5))
; => NIL ;
;    NIL

ends-with-subseq関数: シーケンスの後方一致判定

前節のstarts-with-subseq関数の後方一致版です。

starts-with関数: シーケンスの先頭の要素の一致判定

starts-with-subseq関数は先頭の部分一致を判定しますが、先頭の要素一つだけの判定でいいならstarts-with関数が便利です。
(alexandria:starts-with 1 #(1 2 3))
; => T

一致判定を行う関数に共通ですが、等価性の判定にはデフォルトでeql述語関数が使われているため、文字や文字列などを判定する場合は:testキーワード引数で等価性判定述語を切り替える必要があります。

(alexandria:starts-with #\a "abc" :test #'char=)
; => T

ends-with関数: シーケンスの最後の要素の一致判定

starts-with関数の最後の要素版です。

map-combinations関数: シーケンスの要素の組み合わせに対する繰り返し

高校数学でコンビネーション記号Cを習ったことがある人は、それを思い浮かべてください。Alexandriaのmap-combinations関数は高階関数で、シーケンスの要素の組み合わせを取りながら、その組み合わせに対して関数を適用するというオペレータです。返り値は常に引数のシーケンスなので、副作用を目的に利用されます。

サンプルを見た方が早いと思いますので、以下の例を見てください。

(alexandria:map-combinations #'print '(a b c) :length 2)
; 
; (A B) 
; (A C) 
; (B C) 
; => (A B C)

(alexandria:map-combinations #'print '(a b c d e) :length 2)
; 
; (A B) 
; (A C) 
; (A D) 
; (A E) 
; (B C) 
; (B D) 
; (B E) 
; (C D) 
; (C E) 
; (D E) 
; => (A B C D E)

便利ですが、高校数学で組み合わせの宿題があったとしても、この関数は使わず自分の頭で考えて解きましょう。ただし、Alexandriaの実装を見てアルゴリズムを理解した上で使うなら構わないと思いますが。

map-permutations関数: シーケンスの要素の順列に対する繰り返し

map-combinationsは組み合わせですが、map-permutationsは順列です。どちらも高校数学の範囲です。
(alexandria:map-permutations #'print '(a b c) :length 2)
; 
; (A B) 
; (B A) 
; (A C) 
; (C A) 
; (B C) 
; (C B) 
; => (A B C)

map-derangements関数: シーケンスの要素の完全順列に対する繰り返し

derangementsとは完全順列(撹乱順列)を意味します。完全順列とは、元のシーケンスと並びが被らないような要素の並びです。

これもサンプルを見た方が早いと思うので、以下で示します。

(alexandria:map-derangements #'print '(a b c))
; 
; (C A B) 
; (B C A) 
; => (A B C)

元のシーケンスは'(a b c)という並びなので、1番目が'a、2番目が'b、3番目が'cです。このシーケンスを並び変えた時に、元の順序と一部が一致するような順列は完全順列ではありません。例えば'(a c b)は1番目が'aで元と同じなので、これは完全順列ではありません。

map-derangements関数はこのような完全順列を調べ、その順列に対して第1引数の関数を適用します。

extremum関数: ソートした場合の先頭要素の取得

extremumとは「極値」という意味らしいですが、Alexandriaのextremum関数はそこまで難しい関数ではなく、定義を見る限り「並べ替えた場合の先頭の要素」というくらいの意味のようです。ただし、ソートは行なっていないので、いわゆる「最大値」や「最小値」だけを取得する場合に効率的なのかもしれません。最も、ANSI Common Lispではmaxminが定められているので、数で使うときはベクトルに対して、数以外の場合は比較関数を指定して使う、ということが想定されます。
(alexandria:extremum '(3 2 5 4 1) #'<)
; => 1

(alexandria:extremum '(3 2 5 4 1) #'>)
; => 5

上の例だとリストに対して適用しているので、maxminなどのANSI標準の範囲を使って簡単に書き換えることができます。
(apply #'min '(3 2 5 4 1))
; => 1

(apply #'max '(3 2 5 4 1))
; => 5

ベクトルの場合はloopマクロのminimize, maximize節で対応可能です。
(loop for v across #(3 2 5 4 1) minimize v)
; => 1

(loop for v across #(3 2 5 4 1) maximize v)
; => 5

要素の型が実数(real型)ではない場合は便利かもしれません。なぜならAlexandriaのextremumでは型に応じた比較関数を指定できるからです。
(alexandria:extremum "LISP" #'char<)
; => #\I

0 件のコメント :

コメントを投稿