リスト

要素の参照

リスト要素を参照する関数として代表的なものが、car 関数とcdr 関数です。

AutoLISP関数
(car list)
list : リスト、またはドットリスト
リストの先頭の要素を返します。
戻り値 : アトム、リストまたはドットリスト
AutoLISP関数
(cdr list)
list : リスト、またはドットリスト
リストの先頭の要素を除いた、二番目以降の要素を含むリストを返します。
戻り値 : アトム、リストまたはドットリスト

car 関数は、リストの先頭の要素を返します。言い換えますと、リストを内部的に構成している先頭のコンスセルの一つ目の記憶領域が指す値を返します。コンスセルの一つ目の記憶領域を「car 部」と呼びます。

cdr 関数は、リストの先頭を除いた残りの要素のリストを返します。言い換えますと、リストを内部的に構成している先頭のコンスセルの二つ目の記憶領域が指す値を返します。コンスセルの二つ目の記憶領域を「cdr 部」と呼びます。LISP プログラミングに慣れないうちは二番目以降の要素のリストを返す cdr 関数の存在意義がピンと来ないかもしれませんが、ループやその置き換えでもある再帰関数では便利に使用できるものです。慣れてくると、car 関数と cdr 関数で扱うリストと再帰関数の組み合わせが、とても良くできた仕組みだと実感できるようになるでしょう。

単純な使用例は以下のとおりです。

_$ (setq a '(1 2 3 4 5)) ⏎
(1 2 3 4 5)
_$ (car a) ⏎
1
_$ (cdr a) ⏎
(2 3 4 5)

cdr 関数は、一般的なリストを引数で取っている場合、例え戻り値の要素が一つでも必ずリストの形で返ります。

_$ (setq a '(1 2)) ⏎
(1 2)
_$ (cdr a) ⏎
(2)

しかし、引数がドットリストの中でもドットペアだった場合は、アトムが返ります。

_$ (setq a '(1 . 2)) ⏎
(1 . 2)
_$ (cdr a) ⏎
2

このcdr 関数の、一般的なリストとドットリストの差異は、連想リストで利用されています。

AutoLISP関数
(last list)
list : リスト
リストの最後の要素を返します。
戻り値 : アトム、リストまたはドットリスト

リストの先頭要素を返すのが car 関数でしたが、反対に最後の要素を返す関数が last 関数です。

_$ (setq a '(1 2 3 4 5)) ⏎
(1 2 3 4 5)
_$ (last a) ⏎
5

car 関数がドットリストの引数も受け付けるのに対して、last 関数はドットリストを引数として与えるとエラーが発生します。

car と cdr の派⽣関数

AutoLISP では car と cdr を4 つまで組み合わせた派生した関数が用意されています。LISP 独特の分かりにくい関数ですが、関数名からどのように元の car と cdr に展開できるかが分かりますので、その法則が理解できれば使いこなせます。

派生関数 展開形 説明
(caar list) (car (car list))  
(caaar list) (car (car (car list)  
(caaaar list) (car (car (car (car list))))  
(caaadr list) (car (car (car (cdr list))))  
(caadr list) (car (car (cdr list)))  
(caadar list) (car (car (cdr (car list))))  
(caaddr list) (car (car (cdr (cdr list))))  
(cadr list) (car (cdr list)) リストの 2 番目の要素を返します。
(cadar list) (car (cdr (car list)))  
(cadaar list) (car (cdr (car (car list))))  
(cadadr list) (car (cdr (car (cdr list))))  
(caddr list) (car (cdr (cdr list))) リストの 3 番目の要素を返します。
(caddar list) (car (cdr (cdr (car list))))  
(cadddr list) (car (cdr (cdr (cdr list)))) リストの 4 番目の要素を返します。
(cdar list) (cdr (car list))  
(cdaar list) (cdr (car (car list)))  
(cdaaar list) (cdr (car (car (car list))))  
(cdaadr list) (cdr (car (car (cdr list))))  
(cdadr list) (cdr (car (cdr list)))  
(cdadar list) (cdr (car (cdr (car list))))  
(cdaddr list) (cdr (car (cdr (cdr list))))  
(cddr list) (cdr (cdr list))  
(cddar list) (cdr (cdr (car list)))  
(cddaar list) (cdr (cdr (car (car list))))  
(cddadr list) (cdr (cdr (car (cdr list))))  
(cdddr list) (cdr (cdr (cdr list)))  
(cdddar list) (cdr (cdr (cdr (car list))))  
(cddddr list) (cdr (cdr (cdr (cdr list))))  

car と cdr

car は「カー」、cdr は「クダー」と発音するようです。cddddr などは、どう発音するか不明です。この変な名前は、LISP が生まれた当時のプロセッサの命令から取られています。これらは英単語ではなく、複数の英単語の頭文字をとった略語です。その、もとになった命令の長い名前を知っていても、特別理解が進むわけではありませんのでここでは挙げませんが、興味のある方は検索などして調べてください。

このようなことから、car や cdr といった名前に現在は意味が無くなっています。そのため、Common Lisp では同じ働きをする、first や rest という関数も使用できるようになっています。同じように、二番目三番目の要素を参照する cadr や caddr に対して、second や third が使えるという風に tenth まで Common Lisp では定義されています。

n 番⽬の要素の参照

AutoLISP関数
(nth n list)
n : 整数
list : リスト
リストの n 番目の要素を返します。
戻り値 : アトム、リストまたはドットリスト

nth 関数は、リストの n 番目の要素を返します。先頭のリスト要素のインデックスは 0 です。

_$ (setq a '(0 1 2 3 4 5))⏎
(0 1 2 3 4 5)
_$ (nth 0 a) ⏎
0
_$ (nth 2 a) ⏎
2
_$ (nth 4 a) ⏎
4

インデックスである n 引数が、リストの長さを超えた場合は nil を返し、エラーは発生しません。ただし、list 引数が空リストを表す nil の場合はエラーが発生します。

_$ (setq a '(0 1 2 3 4 5))⏎
(0 1 2 3 4 5)
_$ (nth 6 a) ⏎
nil
_$ (setq a '())⏎
nil
_$ (nth 0 a) ⏎
_1$
; エラー後にリセット

また、list 引数がドットリストの場合は、インデックスの n 引数の値によって、正常に動作したりエラーが発生したりします。

_$ (setq a '(0 1 . 2)) ⏎
(0 1 . 2)
_$ (nth 0 a) ⏎
0
_$ (nth 1 a) ⏎
1
_$ (nth 2 a) ⏎
_1$
; エラー後にリセット

nth 関数は、ドットリストには使用しない方が良いでしょう。

イテレータ

AutoLISP関数
(foreach item list [expr...])
item : シンボル
list : リスト
expr : 式
リストから要素を順に取り出し、式を実行します。
戻り値 : 最後に評価された expr の値

foreach 関数は、list から要素を順に取り出しitem に代入し、[expr…]を実行します。foreach 関数自体の戻り値は、最後に評価された式の値となります。なお、item に代入された値を変更しても、元の対応するリストの要素が書き換わるというようなことは起こりません。

下は、ベクトルの大きさを求める関数です。

(defun vectorLength (v / sum x)
    (setq sum 0.0)
    (sqrt (foreach x v (setq sum (+ sum (* x x)))))
)

関数を実行すると以下のようになります。

_$ (vectorLength '(10.0 10.0)) ⏎
14.1421

リスト要素すべてを対象に処理を行う mapcar という高階関数があります。手続き型的な foreach 関数で書いたアルゴリズムは高階関数で置き換えることができる場合があり、どちらに可読性があり、簡潔に書けるか考慮してください。両者の大きな違いは、foreach 関数では、そこに書いたアルゴリズムにより、集計値やリストといったデータを得たい場合や、副作用のある関数の呼出しであったりします。一方、mapcar 関数は、戻り値として対象のリストを処理した処理前と対応関係があるリストが必ず返ります。

先ほどの手続き型的な vectorLength 関数を、関数型的な mapcar 関数で書き換えると次のようになります。

(defun vectorLength2 (v)
    (sqrt (apply '+ (mapcar '(lambda (x) (* x x)) v)))
)

ソート

リストの高度なソートは、高階関数を使用します。ここでは、高階関数以外の比較的単純な並べ替えの関数を取り上げます。

AutoLISP関数
(reverse list)
list : リスト
リストの順番を反転させたものを返します。
戻り値 : リスト

reverse 関数は、リストの順番を反転させます。

戻り値のリストは、引数とは別のコピーされたリストです。

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

_$ (reverse '(0 1 2 3 4))⏎
(4 3 2 1 0)

ドットリストを反転させようとするとエラーが発生します。

AutoLISP関数
(acad_strlsort list)
list : リスト
文字列のリストを文字コード順にソートします。
戻り値 : リスト

複数の文字列を含んだリストを、文字コード順にソート。します。アルファベットは大文字小文字を区別はしません。

戻り値のリストは、引数とは別のコピーされたリストです。

_$ (acad_strlsort '("orenge" "Banana" "apple"))⏎
("apple" "Banana" "orenge")

日本語でも一応動作します。

_$ (acad_strlsort '("ミカン" "バナナ" "リンゴ"))⏎
("バナナ" "ミカン" "リンゴ")

扱うリストに文字列以外のものが入っていた場合はnil を返します。

_$ (acad_strlsort '("orenge" "Banana" 3)) ⏎
nil

ドットリストをソートしようとすると、nil が返ります。

外部 ObjectARX アプリケーションによる関数 EXRXSUBR です。

検索

AutoLISP関数
(member item list)
item : アトム、リストまたはドットリスト
list : リスト
指定された要素がリスト内に存在するかどうかを検索し、見つかった要素以降のリストを返します。
戻り値 : リスト

member 関数は、リストから指定した要素を検索し、初めに検出した要素以降のリストを返します。要素が検出されずに返すリストが無いときは空のリストを表す nil を返します。

item 引数は、検索する要素を指定します。

list 引数は、検索対象のリストを指定します。ドットリストを指定するとエラーになります。

戻り値は、見つかった要素を含む以降のリストです。

簡単な使用例は、以下のとおりです。

_$ (member "apple" '("banana" "apple" "orenge"))⏎
("apple" "orenge")
_$ (member "lemon" '("banana" "apple" "orenge"))⏎
nil
AutoLISP関数
(vl-position item list)
item : アトム、リストまたはドットリスト
list : リスト
指定された項目のリスト内でのインデックスを返します。
戻り値 : 整数、またはnil

vl-position 関数は、リストから指定した要素を検索し、そのインデックスを返します。先頭のリスト要素のインデックスは 0 (ゼロ)です。

item 引数は、検索する要素を指定します。

list 引数は、検索対象のリストを指定します。ドットリストを指定するとエラーになります。

戻り値は、インデックスを表す整数で、リスト内に見つからなかった場合は nil を返します。

簡単な使用例は、以下のとおりです。

_$ (vl-position "apple" '("banana" "apple" "orenge"))⏎
1
_$ (vl-position "lemon" '("banana" "apple" "orenge"))⏎
nil

置換

AutoLISP関数
(subst newitem olditem list)
newitem,olditem : アトム、リストまたはドットリスト
list : リスト
古い項目を新しい項目で置き換えたリストを返します。
戻り値 : リスト

subst 関数は、リスト内の指定する要素を新しい要素で置換したリストを返します。置換される要素が複数ある場合は、すべて置換されます。置換すべき olditem がリストの中に見つからなかった場合は置換は起こりません。

置換される olditem 引数と置換する newitem 引数は、アトムやリスト、さらにドットリストでも可能です。

list 引数は、処理対象のリストを指定します。ドットリストを指定するとエラーになります。

戻り値の置換後のリストは、引数とは別のコピーされたリストです。

_$ (subst "lemon" "apple" '("banana" "apple" "orenge"))⏎
("banana" "lemon" "orenge")
_$ (subst "lemon" "apple" '("banana" "apple" "orenge" "apple" "orenge"))⏎
("banana" "lemon" "orenge" "lemon" "orenge")

リストの入れ子になっている場合は一番外側の部分だけに作用しますので、次のようなものは意図どおりにはなりません。

_$ (subst "lemon" "apple" '((YELLOW "banana" "apple") (ORANGE "orenge")))⏎
((YELLOW "banana" "apple") (ORANGE "orenge"))

内部のリストを含めたすべてを対象にする場合は、次のような関数を用意します。再帰関数と car と cdr の使用例でもあります。

(defun subst-tree (newitem olditem alist)
  (if (atom alist)
    (if (= alist olditem)
      newitem
      alist
    )
    (cons (subst-tree newitem olditem (car alist))
          (subst-tree newitem olditem (cdr alist))
    )
  )
)

使用例は次のとおりです。

_$ (subst-tree "lemon" "apple" '((YELLOW "banana" "apple") (ORANGE "orenge")))⏎
((YELLOW "banana" "lemon") (ORANGE "orenge"))
AutoLISP の subst 関数は、Common LISP の substitute 関数にあたります。Common LISP の subst 関数は、ここで示した subst-tree 関数にあたります。

削除

AutoLISP関数
(vl-remove item list)
item : アトム、リストまたはドットリスト
list : リスト
リストから要素を除去します。
戻り値 : リスト

vl-remove 関数は、リスト内の指定する要素をリストからすべて削除します。

item 引数は、検索する要素を指定します。

list 引数は、操作対象のリストを指定します。ドットリストを指定するとエラーになります。

戻り値のリストは、引数とは別のコピーされたリストです。除去した結果、リストが空になった場合は nil を返します。

簡単な使用例は、以下のとおりです。

_$ (vl-remove "apple" '("banana" "apple" "orenge"))⏎
("banana" "orenge")
_$ (vl-remove "apple" '("banana" "apple" "orenge" "apple" "orenge" "apple"))⏎
("banana" "orenge" "orenge")

リストの入れ子になっている場合は一番外側の部分だけに作用しますので、次のようなものは意図どおりにはなりません。

_$ (vl-remove "apple" '((YELLOW "banana" "apple") (ORANGE . "orenge")))⏎
((YELLOW "banana" "apple") (ORANGE . "orenge"))

内部のリストを含めたすべてを対象にする場合は、次のような関数を用意します。再帰関数と car と cdr の使用例でもあります。

(defun remove-tree (item alist / temp)
  (if (atom alist)
    alist
    (progn (setq temp (vl-remove item alist))
           (cons (remove-tree item (car temp))
                 (remove-tree item (cdr temp))
           )
    )
  )
)

使用例は次のとおりです。

_$ (remove-tree "apple" '((YELLOW "banana" "apple") (ORANGE "orenge")))⏎
((YELLOW "banana") (ORANGE "orenge"))

リストのコピー

AutoLISP では一切の破壊的関数が排除されているので、リストの加工を行うと必ず元のリストのコピーが加工されます。そのため実際に問題になることはありませんが、リストのコピーについて考えてみます。

リストのコピーを取っておきたい場合に次のようにしても、実際のコピーは作られず、同じリストを指すことになります。

_$ (setq a '(1 (2 3) 4))⏎
(1 (2 3) 4)
_$ (setq b a)⏎
(1 (2 3) 4)
_$ (eq a b)⏎
T

しかしながら、ここでシンボル a からリストを加工して a の値を更新しても、AutoLISP では破壊的な操作は行えないため、シンボル b の内容は (1 (2 3) 4) から変わりません。更新された a の値は元のリストのコピーから作られており、シンボル b は相変わらず元のリストを参照することができるからです。しかし Common LISP で破壊的関数を使ってシンボル a の値を更新しますと、シンボル b の内容も変わることになり、以前の値を保存しておきたかった場合など問題が生じる場合があります。