[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
リスト(list)は、0個以上の(任意のLispオブジェクトの)要素の列を 表現します。 リストとベクトルの重要な相違点は、 複数のリストがそれらの構造の一部を共有できることです。 さらに、リスト全体をコピーすることなく、 リストに要素を追加したり削除できることです。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
Lispのリストは基本データ型ではありません。 リストはコンスセル(cons cells)で構成されます。 コンスセルはドット対を表現するデータオブジェクトです。 ドット対は2つのLispオブジェクトを保持、つまり、『指し』ます。 その2つのLispオブジェクトの一方をCAR、他方をCDRといいます。 これらの名前は歴史的なものです。 See 節 2.3.6 コンスセルとリスト型。 CDRは『クダー』と読みます。
リストはコンスセルを連ねたものであり、 リストの各要素ごとにコンスセルが1つあります。 慣習として、コンスセルのCARはリストの要素であり、 CDRはリストを繋ぐために使います。 つまり、各コンスセルのCDRは後続のコンスセルです。 最後のコンスセルのCDRはnil
です。 CARとCDRの非対称性は単なる慣習によるものです。 コンスセルのレベルでは、CARとCDRには同じ性質があります。
ほとんどのコンスセルはリストの一部として使われるので、 リスト構造(list structure)という用語は、 コンスセルで構成した任意の構造を意味するようになりました。
シンボルnil
は、シンボルであるとともにリストでもあるとみなします。 これは要素を持たないリストです。 慣習として、シンボルnil
のCDR(およびCAR)は nil
であるとみなします。
空でない任意のリストlのCDRは、 lの先頭要素を除くすべての要素を含んだリストです。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
コンスセルは1対の箱で図示できます。 最初の箱はCARを表し、2番目の箱はCDRを表します。 つぎは、2つのコンスセルから成る 2要素のリスト(tulip lily)
を図示したものです。
--------------- --------------- | car | cdr | | car | cdr | | tulip | o---------->| lily | nil | | | | | | | --------------- --------------- |
各1対の箱がコンスセルを表します。 各箱は、Lispオブジェクトを『参照する』、『指す』、『含む』のです。 (これらの用語は同義語。) 最初のコンスセルのCARを表す最初の箱は、 シンボルtulip
を含みます。 最初のコンスセルのCDR箱から2番目のコンスセルへ向かう矢印は、 最初のコンスセルのCDRが2番目のコンスセルであることを表します。
同じリストは、つぎのような別の箱記法でも図示できます。
--- --- --- --- | | |--> | | |--> nil --- --- --- --- | | | | --> tulip --> lily |
つぎは、より複雑で、最初の要素が2要素リストであるような 3要素リストを図示したものです。
--- --- --- --- --- --- | | |--> | | |--> | | |--> nil --- --- --- --- --- --- | | | | | | | --> oak --> maple | | --- --- --- --- --> | | |--> | | |--> nil --- --- --- --- | | | | --> pine --> needles |
同じリストを最初の箱記法で表現するとつぎのようになります。
-------------- -------------- -------------- | car | cdr | | car | cdr | | car | cdr | | o | o------->| oak | o------->| maple | nil | | | | | | | | | | | -- | --------- -------------- -------------- | | | -------------- ---------------- | | car | cdr | | car | cdr | ------>| pine | o------->| needles | nil | | | | | | | -------------- ---------------- |
コンスセルとリストの入力構文と表示表現、および、 『箱と矢印』によるリストの図示については、See 節 2.3.6 コンスセルとリスト型
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
以下の述語は、Lispオブジェクトが、アトムであるか、 コンスセル、つまり、リストであるか、 特別なオブジェクトnil
であるか調べます。 (これらの多く述語は、それぞれ残りの述語で定義可能である。 しかし、多用するため、これらすべてを用意しておく価値がある。)
t
を返し、 さもなければnil
を返す。 nil
はコンスセルではないが、空リストである。t
を返し、 さもなければnil
を返す。 コンスセルを除くすべてのオブジェクトはアトムである。 シンボルnil
はアトムでもありリストでもある。 このようなLispオブジェクトはnil
だけである。
(atom object) == (not (consp object)) |
nil
ならばt
を返す。 さもなければnil
を返す。
(listp '(1)) => t (listp '()) => t |
listp
の反対である。 objectがリストでなければt
を返す。 さもなければnil
を返す。
(listp object) == (not (nlistp object)) |
nil
ならばt
を返し、 さもなければnil
を返す。 この関数は、not
と同一であるが、意図を明確にするために、 objectをリストと考えるときにはnull
を使い、 objectを真理値と考えるときにはnot
を使う (9.3 条件の組み合わせのnot
を参照)
(null '(1)) => nil (null '()) => t |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
特別な場合として、cons-cellがnil
のときには、 car
はnil
を返すと定義する。 したがって、任意のリストはcar
の正しい引数である。 引数がコンスセルでもnil
でもなければエラーを通知する。
(car '(a b c)) => a (car '()) => nil |
特別な場合として、cons-cellがnil
のときには、 cdr
はnil
を返すと定義する。 したがって、任意のリストはcdr
の正しい引数である。 引数がコンスセルでもnil
でもなければエラーを通知する。
(cdr '(a b c)) => (b c) (cdr '()) => nil |
nil
を返す。 これはcar
と対照的であり、 car
はobjectがリストでないとエラーを通知する。
(car-safe object) == (let ((x object)) (if (consp x) (car x) nil)) |
nil
を返す。 これはcdr
と対照的であり、 cdr
はobjectがリストでないとエラーを通知する。
(cdr-safe object) == (let ((x object)) (if (consp x) (cdr x) nil)) |
nil
になる。
nが負であると、nth
はlistの最初の要素を返す。
(nth 2 '(1 2 3 4)) => 3 (nth 10 '(1 2 3 4)) => nil (nth -3 '(1 2 3 4)) => 1 (nth n x) == (car (nthcdr n x)) |
関数elt
も同様であるが、任意のシーケンスに適用できる。 歴史的な理由で引数の順序は逆である。 see 節 6.1 シーケンス。
nが0か負であると、nthcdr
はlist全体を返す。 listの長さがnかそれ未満であると、 nthcdr
はnil
を返す。
(nthcdr 1 '(1 2 3 4)) => (2 3 4) (nthcdr 10 '(1 2 3 4)) => nil (nthcdr -3 '(1 2 3 4)) => (1 2 3 4) |
listが実際にはリストでない場合には、safe-length
は0を返す。 listに循環があると、少なくとも異なる要素の個数を表す有限値を返す。
循環はないと思われるリストの長さを計算するもっとも一般的な方法は、 length
です。 See 節 6.1 シーケンス。
(car (car cons-cell))
と同じ。(car (cdr cons-cell))
や (nth 1 cons-cell)
と同じ。(cdr (car cons-cell))
と同じ。(cdr (cdr cons-cell))
や (nthcdr 2 cons-cell)
と同じ。[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
リストはLispの中核なので、多くの関数はリストを構築します。 cons
は基本的なリスト構築関数です。 しかし、Emacsのソースコードでは、cons
よりlist
を 多用していることは興味深いことです。
(cons 1 '(2)) => (1 2) (cons 1 '()) => (1) (cons 1 2) => (1 . 2) |
cons
は、リストの先頭に要素を1つ追加するために しばしば使われる。 これを要素をリストにコンスするという。 たとえば、つぎのとおり。
(setq list (cons newelt list)) |
この例におけるlist
という名前の変数と 以下に述べるlist
という名前の関数とは衝突しない。 任意のシンボルはどちらの目的にも使える。
nil
終端になる。 objectsを指定しないと空リストを返す。
(list 1 2 3 4 5) => (1 2 3 4 5) (list 1 2 '(3 4 5) 'foo) => (1 2 (3 4 5) foo) (list) => nil |
make-string
と比較してほしい(see 節 4.3 文字列の作成)。
(make-list 3 'pigs) => (pigs pigs pigs) (make-list 0 'pigs) => nil |
nconc
を参照。)
一般には、append
の最後の引数はどんなLispオブジェクトでもよい。 最後の引数をコピーしたり変換したりしない。 それは、新たなリストの最後のコンスセルのCDRになる。 最後の引数がそれ自体リストであれば、それらの要素は、実質的には、 結果のリストの要素になる。 最後の要素がリストでなければ、結果は『ドット対』になる。 なぜなら、結果の最後のCDRは、 真のリストに必要とされるnil
ではないからである。
関数append
は、引数として整数も受け付ける。 整数を10進の表示表現の文字列に変換してから、 その文字列を整数のかわりに使う。 この機能を使わないでほしい。 削除する予定である。 読者がこの機能を使っていたら、今すぐプログラムを直すこと! 整数をこのような10進数に変換する正しい方法は、
format
(see 節 4.7 文字列の書式付け)や number-to-string
(see 節 4.6 文字と文字列の変換)を使うことである。
append
の使用例をつぎに示します。
(setq trees '(pine oak)) => (pine oak) (setq more-trees (append '(maple birch) trees)) => (maple birch pine oak) trees => (pine oak) more-trees => (maple birch pine oak) (eq trees (cdr (cdr more-trees))) => t |
箱表示を見ればappend
の動作を理解できるでしょう。 変数trees
にリスト(pine oak)
を設定し、ついで、 変数more-trees
にはリスト(maple birch pine oak)
を設定します。 しかし、変数trees
はもとのリストを指し続けます。
more-trees trees | | | --- --- --- --- -> --- --- --- --- --> | | |--> | | |--> | | |--> | | |--> nil --- --- --- --- --- --- --- --- | | | | | | | | --> maple -->birch --> pine --> oak |
空シーケンスはappend
が返す値にはまったく寄与しません。 この結果、最後のnil
引数は直前の引数をコピーするように強制します。
trees => (pine oak) (setq wood (append trees nil)) => (pine oak) wood => (pine oak) (eq wood trees) => nil |
この方法は、関数copy-sequence
を導入するまでは、 リストをコピーする普通の方法でした。 See 節 6. シーケンス、配列、ベクトル。
append
の引数にベクトルと文字列を使った例をつぎに示します。
(append [a b] "cd" nil) => (a b 99 100) |
apply
(see 節 11.5 関数呼び出し)の助けを借りれば、 リストのリストの中にあるすべてのリストを連結できます。
(apply 'append '((a b c) nil (x y z) nil)) => (a b c x y z) |
sequencesをまったく指定しないとnil
を返します。
(append) => nil |
最後の引数がリストではない例をいくつか示します。
(append '(x y) 'z) => (x y . z) (append '(x y) [z]) => (x y . [z]) |
最後の引数がリストではなくシーケンスである2番目の例は、 シーケンスの要素が結果のリストの要素にはならないことを示しています。 そのかわりに、最後の引数がリストでない場合と同様に、 シーケンスが最後のCDRになります。
(setq x '(1 2 3 4)) => (1 2 3 4) (reverse x) => (4 3 2 1) x => (1 2 3 4) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
基本関数setcar
やsetcdr
を使って、 コンスセルのCARやCDRの内容を変更できます。 これらは、既存のリスト構造を変更するので、 『破壊的』な操作と呼びます。
Common Lispに関した注意:Common Lispでは、 リスト構造を変更するには
rplaca
やrplacd
を使う。 これらはsetcar
やsetcdr
と同様に構造を変更する。 しかし、Common Lispの関数はコンスセルを返すが、setcar
やsetcdr
は新たなCARやCDRを返す。
5.6.1 setcar
によるリスト要素の変更Replacing an element in a list. 5.6.2 リストのCDRの変更 Replacing part of the list backbone. This can be used to remove or add elements. 5.6.3 リストの順序を変更する関数 Reordering the elements in a list; combining lists.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
setcar
によるリスト要素の変更setcar
によるリスト要素の変更"へのコメント(無し)
コンスセルのCARを変更するには、setcar
を使います。 リストに対して使用すると、 setcar
はリストの1つの要素を別の要素に置き換えます。
(setq x '(1 2)) => (1 2) (setcar x 4) => 4 x => (4 2) |
コンスセルが複数のリストの共有構造の一部であるときには、 コンスセルに新たなCARを格納すると、 そのような各リストの1つの要素を変更することになります。
;; 共有部分がある2つのリストを作る (setq x1 '(a b c)) => (a b c) (setq x2 (cons 'z (cdr x1))) => (z b c) ;; 共有部分のCARを置き換える (setcar (cdr x1) 'foo) => foo x1 ; 両方のリストが変更されている => (a foo c) x2 => (z foo c) ;; 非共有部分のCARを置き換える (setcar x1 'baz) => baz x1 ; 1つのリストだけが変更されている => (baz foo c) x2 => (z foo c) |
変数x1
とx2
に入っている共有部分を持つ2つのリストを図示すると つぎのようになります。 b
を置き換えるとなぜ両者が変更されるのかわかるでしょう。
--- --- --- --- --- --- x1---> | | |----> | | |--> | | |--> nil --- --- --- --- --- --- | --> | | | | | | --> a | --> b --> c | --- --- | x2--> | | |-- --- --- | | --> z |
同じ関係を別の箱表示で示します。
x1: -------------- -------------- -------------- | car | cdr | | car | cdr | | car | cdr | | a | o------->| b | o------->| c | nil | | | | -->| | | | | | -------------- | -------------- -------------- | x2: | -------------- | | car | cdr | | | z | o---- | | | -------------- |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
CDRを修正するもっとも低レベルの基本関数はsetcdr
です。
リストのCDRを別のリストで置き換える例を示します。 リストの最初の要素以外は取り除かれ、 要素の別のシーケンスになります。 最初の要素は変更されません。 というのは、それはリストのCARの中にあり、 CDRからは辿れないからです。
(setq x '(1 2 3)) => (1 2 3) (setcdr x '(4)) => (4) x => (1 4) |
リスト内のコンスセル群のCDRを変更することで、 リストの中ほどの要素を削除できます。 つぎの例は、リスト(a b c)
の最初のコンスセルのCDRを変更することで、 このリストの第2要素b
を削除します。
(setq x1 '(a b c)) => (a b c) (setcdr x1 (cdr (cdr x1))) => (c) x1 => (a c) |
箱表記では、この結果はつぎのようになります。
-------------------- | | -------------- | -------------- | -------------- | car | cdr | | | car | cdr | -->| car | cdr | | a | o----- | b | o-------->| c | nil | | | | | | | | | | -------------- -------------- -------------- |
以前に要素b
を保持していた2番目のコンスセルはまだ存在していて、 そのCARもまだb
ですが、このリストの一部ではありません。
CDRを変更して新たな要素を挿入するのも同様に簡単です。
(setq x1 '(a b c)) => (a b c) (setcdr x1 (cons 'd (cdr x1))) => (d b c) x1 => (a d b c) |
箱表記では、この結果はつぎのようになります。
-------------- ------------- ------------- | car | cdr | | car | cdr | | car | cdr | | a | o | -->| b | o------->| c | nil | | | | | | | | | | | | --------- | -- | ------------- ------------- | | ----- -------- | | | --------------- | | | car | cdr | | -->| d | o------ | | | --------------- |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
以下は、リストを構成するコンスセルのCDRを変更することで、 『破壊的に』リストの順序を変更する関数です。 これらの関数を『破壊的』と呼ぶのは、 渡された引数であるもとのリストのコンスセルを繋ぎ換えて新たなリストに 変えるからです。
コンスセルを変更する他の関数については、 5.7 集合としてのリストの利用のSee delq
を参照してください。
append
(see 節 5.5 コンスセルとリストの構築)と異なり、 listsをコピーしない。 そのかわりに、各listsの最後のCDRを後続のリストを指すように変更する。 listsの最後は変更しない。 たとえば、つぎのようになる。
(setq x '(1 2 3)) => (1 2 3) (nconc x '(4 5)) => (1 2 3 4 5) x => (1 2 3 4 5) |
nconc
は最後の引数を変更しないので、 上述の例のように、'(4 5)
などの定数リストを使ってよい。 同じ理由で最後の引数はリストである必要もない。
(setq x '(1 2 3)) => (1 2 3) (nconc x 'z) => (1 2 3 . z) x => (1 2 3 . z) |
しかしながら、すべての引数は(最後のものを除いて)リストである必要がある。
よくある落し穴は、nconc
の最後以外の引数に、 クォートした定数リストを使うことである。 こうすると、読者のプログラムは実行するたびに定数を変えてしまう。 たとえば、つぎのようになる。
(defun add-foo (x) ; この関数は引数の先頭に
(nconc '(foo) x)) ;
|
reverse
と異なり、nreverse
は リストを構成するコンスセルのCDRを逆向きにして引数を変えてしまう。 listの最後にあったコンスセルは戻り値の最初のコンスセルになる。
たとえば、つぎのようになる。
(setq x '(1 2 3 4)) => (1 2 3 4) x => (1 2 3 4) (nreverse x) => (4 3 2 1) ;; 先頭にあったコンスセルは、今、最後になっている x => (1) |
混乱を避けるために、nreverse
の結果は、 もとのリストを収めていたものと同じ変数に格納する。
(setq x (nreverse x)) |
nreverse
を(a b c)
に適用した結果を図示すると つぎのようになる。
もとのリストの先頭 逆順にしたリスト ------------- ------------- ------------ | car | cdr | | car | cdr | | car | cdr | | a | nil |<-- | b | o |<-- | c | o | | | | | | | | | | | | | | ------------- | --------- | - | -------- | - | | | | ------------- ------------ |
引数predicateは、2つの引数を取る関数である必要がある。 この関数は、listの2つの要素で呼び出される。 昇順のソートでは、predicateは、 第1引数が第2引数より『小さい』ときにt
を返し、 さもなければnil
を返す必要がある。
比較関数predicateは、少なくとも単一のsort
の呼び出し中は、 引数の任意の対に対して信頼できる結果を返す必要がある。 まず、反対称であること。 つまり、aがbより小さいときには、 bがaより小さくてはいけない。 また、遷移則が成り立つこと。 つまり、aがbより小さく、かつ、bがcより小さいときには、 aはcより小さくなければならない。 これらの要請を満たさない比較関数を用いると、 sort
の結果は予測できない。
sort
が破壊的であるというのは、 listを構成するコンスセルのCDRを変更して、 コンスセルの順序を変更するからである。 非破壊的なソート関数では、ソートした要素を格納するために新たなコンスセルを 作成するであろう。 もとのリストを破壊せずにソートしたければ、 まずcopy-sequence
でコピーを作り、それをソートする。
ソートする際、listのコンスセルのCARは変更しない。 list内の要素a
を入れていたコンスセルは、 ソート後にもそのCARにはa
が入っている。 しかし、CDRを変更してあるので、リスト内では異なる場所に現れる。 たとえば、つぎのようになる。
(setq nums '(1 3 2 6 5 4 0)) => (1 3 2 6 5 4 0) (sort nums '<) => (0 1 2 3 4 5 6) nums => (1 2 3 4 5 6) |
警告: nums
のリストには 0が入っていないことに注意。 (nums
が指す)コンスセルはソート前と同じコンスセルだが、 それはもはやリストの先頭にはない。 引数を保持していた変数が、 ソートしたリスト全体を保持していると仮定しないこと! かわりに、
sort
の結果を保存して、それを使う。 多くの場合、つぎのように、もとのリストを保持していた変数に結果を保存し直す。
(setq nums (sort nums '<)) |
ソートを行う他の関数については、see 節 31.15 テキストのソート。 sort
の有用な例については、 23.2 説明文字列の参照のdocumentation
を参照。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
リストで、数学の順序のない集合を表現できます。 つまり、リストに現れる要素を集合の要素と考え、 リスト内での順序は無視します。 2つの集合の和集合を作るには、 (要素が重複することを気にしなければ)append
を使います。 集合向けの他の有用な関数には、memq
やdelq
、および、 これらのequal
版であるmember
やdelete
があります。
Common Lispに関した注意:Common Lispには、集合演算向けに (要素の重複を避ける)関数
union
とintersection
があるが、 GNU Emacs Lispにはない。 必要ならば、読者みずからLispでこれらを書ける。
memq
はobjectが最初に現れるところから始まるリストを返す。 さもなければnil
を返す。 memq
の文字`q'は、リストの要素に対するobjectの比較に eq
を使うことを意味する。 たとえば、
(memq 'b '(a b c b a)) => (b c b a) (memq '(2) '((1) (2))) ; |
eq
であるすべての要素を 破壊的に削除する。 delq
の文字`q'は、memq
と同様に、 リストの要素に対するobjectの比較にeq
を使うことを意味する。delq
がリストの先頭から要素を削除する場合には、 単にリストを辿って削除した要素のつぎから始まる部分リストを返します。
(delq 'a '(a b c)) == (cdr '(a b c)) |
リストの中ほどの要素を削除する場合には、 削除にはCDRの変更を伴います(see 節 5.6.2 リストのCDRの変更)。
(setq sample-list '(a b c (4))) => (a b c (4)) (delq 'a sample-list) => (b c (4)) sample-list => (a b c (4)) (delq 'c sample-list) => (a b (4)) sample-list => (a b (4)) |
(delq 'c sample-list)
は、 3番目の要素を切り取ってsample-list
を変更しますが、 (delq 'a sample-list)
では、 なにも切り取らずに単に短いリストを返すことに注意してください。 引数listを保持していた変数が、実行後には少ない要素を持つと仮定したり、 もとのリストを保持し続けていると仮定したりしないでください! そのかわりに、
delq
の結果を保存して、それを使ってください。 多くの場合、つぎのように、 もとのリストを保持していた変数に結果を保存し直します。
(setq flowers (delq 'rose flowers)) |
つぎの例では、delq
が一致を取ろうとしている(4)
と sample-list
の(4)
とはeq
ではありません。
(delq '(4) sample-list) => (a c (4)) |
つぎの2つの関数は、memq
やdelq
に似ていますが、 比較にはeq
のかわりにequal
を使います。 See 節 2.6 同値述語。
member
は、equal
を使ってobjectと要素を比較して、 objectがlistの要素かどうか調べる。 objectが要素であれば、 member
はlist内でそれが最初に現れるところから始まるリストを返す。 さもなければnil
を返す。
memq
と比較してほしい。
(member '(2) '((1) (2))) ; |
equal
であるすべての要素を 破壊的に削除する。 member
がmemeq
に対応するように、delq
に対応する。 member
と同様に、 要素とobjectとの比較にはequal
を使う。 一致する要素をみつけると、delq
と同様に要素を削除する。 たとえば、つぎのとおり。
(delete '(2) '((2) (1) (2))) => ((1)) |
Common Lispに関した注意:GNU Emacs Lispの関数
member
と関数delete
は Maclispから受け継いだものであり、Common Lispからではない。 Common Lisp版では要素の比較にはequal
を使わない。
変数に格納したリストに要素を追加する別の方法については、 10.8 変数値の変更の関数add-to-list
を参照してください。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
連想リスト(association list)、略してalistは、 キーから値への対応付けを記録しています。 これは連想(associations)と呼ばれるコンスセルのリストです。 各コンスセルのCARはkeyであり、 CDRは連想値(associated value)です。 (6)
連想リストの例を示します。 キーpine
を値cones
に、キーoak
を値acorns
に、 キーmaple
を値seeds
に対応付けています。
'((pine . cones) (oak . acorns) (maple . seeds)) |
連想リスト内の連想値は任意のLispオブジェクトでよく、キーもそうです。 たとえば、つぎの連想リストでは、シンボルa
に数1
を、 文字列"b"
にリスト(2 3)
を対応付けています。 リスト(2 3)
は連想リストの要素のCDRです。
((a . 1) ("b" 2 3)) |
要素のCDRのCARに連想値を格納するように 連想リストを設計したほうがよい場合もあります。 つぎのようにします。
'((rose red) (lily white) (buttercup yellow)) |
ここで、red
はrose
に対応付けた値と考えます。 この種の連想リストの利点の1つは、関連する別の情報を、 他の項目から成るリストでさえも、CDRのCDRに格納できることです。 1つの欠点は、rassq
(下記参照)を使って 指定した値を含む要素を探せないことです。 これらの条件が重要でない場合には、1つの連想リストに関する限り、 一貫性があればどちらを選ぶかは好みの問題です。
上に示した連想リストは、要素のCDRに連想値が収めてあると 考えることもできます。 rose
の連想値はリスト(red)
になります。
連想リストはスタックなどに置くような情報の記録に使います。 というには、リストの先頭に新たな連想を追加するのが簡単だからです。 指定したキーに対する連想を連想リストから探すとき、 それらが複数個存在する場合には、最初にみつかったものを返します。
Emacs Listでは、連想リストの要素がコンスセルでなくても エラーではありません。 連想リスト探索関数はそのような要素を単に無視します。 他の多くのLispでは、そのような場面ではエラーを通知します。
属性リストもいろいろな意味で連想リストに類似しています。 属性リストは、キーが一度しか現れない連想リストのようにふるまいます。 属性リストと連想リストの比較については、See 節 7.4 属性リスト。
equal
(see 節 2.6 同値述語)を用いる。 alistの中にCARがkeyにequal
である連想が 存在しなければ、nil
を返す。 たとえば、つぎのとおり。
(setq trees '((pine . cones) (oak . acorns) (maple . seeds))) => ((pine . cones) (oak . acorns) (maple . seeds)) (assoc 'oak trees) => (oak . acorns) (cdr (assoc 'oak trees)) => acorns (assoc 'birch trees) => nil |
つぎは、キーと値がシンボルではない例。
(setq needles-per-cluster '((2 "Austrian Pine" "Red Pine") (3 "Pitch Pine") (5 "White Pine"))) (cdr (assoc 3 needles-per-cluster)) => ("Pitch Pine") (cdr (assoc 2 needles-per-cluster)) => ("Austrian Pine" "Red Pine") |
関数assoc-ignore-representation
とassoc-ignore-case
は assoc
に似ていますが、 それらは比較にcompare-strings
を使う点が異なります。 See 節 4.5 文字と文字列の比較。
equal
である連想が 存在しなければ、nil
を返す。
rassoc
はassoc
に似ているが、 alistの各連想のCARのかわりにCDRを比較する点が異なる。 指定した値に対するキーを探す『assoc
の逆演算』と考えることができる。
assoc
に似ているが、equal
のかわりにeq
で比較する。 alist内の連想のCARがkeyにeq
であるものが存在しないと、 assq
はnil
を返す。 この関数はassoc
より多用される。 というのは、eq
はequal
より高速であり、 ほとんどの連想リストではキーとしてシンボルを使うからである。
(setq trees '((pine . cones) (oak . acorns) (maple . seeds))) => ((pine . cones) (oak . acorns) (maple . seeds)) (assq 'pine trees) => (pine . cones) |
一方で、キーがシンボルではない連想リストでは、 assq
は、通常、有用ではない。
(setq leaves '(("simple leaves" . oak) ("compound leaves" . horsechestnut))) (assq "simple leaves" leaves) => nil (assoc "simple leaves" leaves) => ("simple leaves" . oak) |
eq
である連想が 存在しなければ、nil
を返す。
rassq
はassq
に似ているが、 alistの各連想のCARのかわりにCDRを比較する点が異なる。 指定した値に対するキーを探す『assq
の逆演算』と考えることができる。
たとえばつぎのとおり。
(setq trees '((pine . cones) (oak . acorns) (maple . seeds))) (rassq 'acorns trees) => (oak . acorns) (rassq 'spores trees) => nil |
rassq
では、 要素のCDRのCARに格納された値を探せないことに注意。
(setq colors '((rose red) (lily white) (buttercup yellow))) (rassq 'white colors) => nil |
この場合、連想(lily white)
のCDRは、 シンボルwhite
ではなくリスト(white)
である。 連想をドット対記法で書くとこれが明確になる。
(lily white) == (lily . (white)) |
string-match
を使うと有益な結果を得られる。 testを省略したりnil
であると、比較にはequal
を用いる。
上の条件で連想リストの要素がkeyに一致するならば、 assoc-default
はその要素に基づく値を返す。 要素がコンスならば値は要素のCDR。 さもなければ、戻り値はdefault。
keyに一致する連想リストの要素が存在しなければ、 assoc-default
はnil
を返す。
(setq needles-per-cluster '((2 . ("Austrian Pine" "Red Pine")) (3 . ("Pitch Pine")) (5 . ("White Pine")))) => ((2 "Austrian Pine" "Red Pine") (3 "Pitch Pine") (5 "White Pine")) (setq copy (copy-alist needles-per-cluster)) => ((2 "Austrian Pine" "Red Pine") (3 "Pitch Pine") (5 "White Pine")) (eq needles-per-cluster copy) => nil (equal needles-per-cluster copy) => t (eq (car needles-per-cluster) (car copy)) => nil (cdr (car (cdr needles-per-cluster))) => ("Pitch Pine") (eq (cdr (car (cdr needles-per-cluster))) (cdr (car (cdr copy)))) => t |
この例は、copy-alist
により、 コピーの連想を変更して他のものになぜ影響しないかを示す。
(setcdr (assq 3 copy) '("Martian Vacuum Pine")) (cdr (assq 3 needles-per-cluster)) => ("Pitch Pine") |
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |