連想リスト

連想リストとは

連想リストは、他のリストとは異なる別のものではありません。その形式が、最初の項目を識別子とし後続のデータとグループ化したペアを、要素としてリストにまとめたものを連想リスト(Association list)と言います。そのため、ほとんどの操作はリストの章で紹介した関数を使用し、連想リスト専用で新しく出てくるのは assoc 関数のみです。

連想リストの例として、entget 関数で得られるAutoCAD の図面データベースから得られる図形データは、DXF のグループコードを識別子とした連想リストの形で提供されます。例えば直線の情報は下のようなものです。ドットリストが出てきます。

( (-1 . <図形名: 7ffff706140>)
  (0 . "LINE")
  (330 . <図形名: 7ffff703980>)
  (5 . "2A4")
  (100 . "AcDbEntity")
  (67 . 0)
  (410 . "Model")
  (8 . "0")
  (100 . "AcDbLine")
  (10 0.0 0.0 0.0)
  (11 10.0 10.0 0.0)
  (210 0.0 0.0 1.0)
)

DXF グループコードの 0 は図形のタイプ、8 は画層名、10 と 11 は頂点の座標といった具合に読み解きます。識別子は、整数に限りません。"banana"のような文字列でも、あるいは(”apple" 10)のようなリストでも可能で、どのようなものであれ第一要素が識別子としてあつかわれます。

なお、連想リストの要素の順番は関係ないように思えますが、AutoCAD の図形データでは要素の順番で AutoCAD の解釈がうまくいったり失敗したりすることが時にはあるので、頭の隅に入れておいてください。図形データベースから得られた順で扱えば問題は起こりません。

連想リストの作法

連想リストを扱うときに、多少の作法があります。先ほどの例で「(0 . "LINE")」のようにドットリストになっているものがあります。また、「(10 0.0 0.0 0.0)」のようにドットが出現していないものもあります。例では出てきませんがドットの無い「(999 1000)」というものや要素が一つの「(70)」というのもあり得ます。これらの違いは以下の表のようにまとめられます。

  識別子 データ
(70) 70 nil
(0 . "LINE") 0 "LINE"
(999 1000) 999 (1000)
(10 0.0 0.0 0.0) 10 (0.0 0.0 0.0)

要素が二つの「(0 . "LINE")」と「(999 1000)」に注目してください。このように、ドットリストでないものは、データがリストであることを示しています。

このような違いがあることを知ったうえで、グループを作成するための関数は、すべての場合でcons 関数を以下のように使用することで対応できます。

(cons 識別子 データ)

(70)」は (cons 70 nil) で得られます。「(0 . "LINE")」を作成したいのであれば、(cons 0 "LINE") です。「(10 0.0 0.0 0.0)」を作成したいのであれば、(cons 10 '(0.0 0.0 0.0)) です。そして、「(999 1000)」を作成したいのであれば、(cons 999 '(1000)) となります。

次に「(0 . "LINE")」から識別子とデータを分離して取り出す方法について考えてみます。識別子を取り出すのは car 関数で、すべての場合で通用します。

(car '(0 . "LINE")) → 0

データを取り出したい場合はcdr 関数を使えば、すべての場合に対応できます。

(cdr '(0 . "LINE")) → “LINE”

cdr 関数にかければ、ドットリストの場合もそうでない場合も問題は起こらない上、アトムの場合も要素が一つのリストの場合も区別してデータを取り出すことができます。

(cdr '(999 1000)) → (1000)
(cdr '(10 0.0 0.0 0.0)) → (0.0 0.0 0.0)

このように、連想リストの要素の作成は cons 関数、識別子の取り出しは car 関数、データの取り出しは cdr 関数を使ってください。そして、それらの要素を連想リストとして編成するのは list 関数などを通常のリストと同じように使用します。

データの抽出

連想リストから、識別子をインデックスとして要素を取り出す関数が LISP には用意されています。

AutoLISP関数
(assoc element alist)
element:アトム、リストまたはドットリスト
alist:連想リスト
連想リストの要素を検索し、指定された要素が含まれる連想リスト項目を返します。
戻り値: リストまたはドットリスト

assoc 関数は、連想リスト内から指定された識別子をもった要素を返します。

element 引数は識別子を指定します。アトムでもリストでも識別子になります。

alist 引数はデータを検索する連想リストを指定します。

戻り値は、識別子とデータを含んだリストが返りますが、該当する識別子が無かった場合は nil が返ります。

_$ (assoc 0 '((0 . "LINE") (8 . "0") (10 0.0 0.0 0.0))) ⏎
(0 . "LINE")

連想リストに同じ識別子のデータが複数含まれている場合は、assoc 関数は最初に見つかった要素を返します。これは、後ろにあるデータにアクセスするためには特別にコードを書かなければならないことを示します。そして、AutoCAD の図形データの中には DXF グループコード 100 など、複数含まれるものがあります。

AutoCAD から図形データを抽出する実際の例は以下のようになります。

(setq sel nil)
(while (= sel nil)                                 ; ユーザーによる図形の選択
    (setq sel (entsel "¥n 図形を一つ選択してください"))
)
(setq name     (car sel))                          ; 選択図形の【図形名】をname に格納
(setq data (entget name))                          ; 【図形名】から詳細な図形データを取得
(setq layer (cdr (assoc 8 data)))                  ; 図形データから画層情報 8 を抽出
(prompt "¥n 画層は ")
(prompt layer)
(prompt " です。")

上記の実行結果は、ユーザーの選択図形を取得し、コマンドラインで次のように表示されます。

図形を一つ選択してください                ;ユーザが図形を選択
画層は 0 です。

データの置換

連想リストを変更するには、通常のリストと同じように subst 関数を使用して連想リストの要素を置き換えます。

次は、連想リストの該当の識別子のデータを subst 関数で置き換える関数の例です。元のリストに該当する識別子が無かった場合は append 関数で連想リストに追加しています。

(defun setAList (alist index data / old)
    (if (setq old (assoc index alist))         ; assoc の戻り値が nil 以外か?
        (subst (cons index data) old alist)    ;   then 置き換え
        (append alist (list (cons index data)) ;   else 追加
    )
)

関数の使用例は以下のとおりです。

_$ (setAList '((0 . "LINE") (8 . "0") (10 0.0 0.0 0.0)) 8 "10")⏎
((0 . "LINE") (8 . "10") (10 0.0 0.0 0.0))
_$ (setAList '((0 . "LINE") (8 . "0") (10 0.0 0.0 0.0)) 11 '(10.0 10.0 10.0))⏎
((0 . "LINE") (8 . "10") (10 0.0 0.0 0.0) (11 10.0 10.0 10.0))