IR.0-4の日記: emacs lispのリスト中抜き関数 3
'(1 2 3 4) -> '(1 3 4)
とか
'(a b c d) -> '(b c d)
と言った中抜きの実装
--------------------------------------------------------------------------------
(defmacro nakanuki (index list)
"リスト list の index の要素を破壊的に削除する。そのリストを返す。
最初の要素を指す index の値は 0。
index が 負数の場合はエラー。"
`(progn
(cond
( (< ,index 0)
(error "nakanuki の引数 index は、0以上でなければならない" )
)
( (= ,index 0)
(let ((len (length ,list)))
(cond
( (= len 0) nil );; return
( (= len 1) (setcar ,list nil)
(if (symbolp (quote ,list))
(progn
(setq ,list (cdr ,list) )
,list );; return
(cdr ,list) ) ) ;; return
( t (setcar ,list (car (cdr ,list)))
(setcdr ,list (nthcdr 2 ,list))
,list ) ) ) ) ;; return
( t
(setcdr (nthcdr (1- ,index) ,list) (nthcdr (1+ ,index) ,list) )
,list ) ) ) ) ;; return
--------------------------------------------------------------------------------
Case 1 : '(1 2 3 4) -> '(1 3 4)するテスト。
コンス破壊なので、同じコンスを指す変数の内容も変更される。
--------------------------------------------------------------------------------
(nakanuki 1 '(1 2 3 4)) ;; => (1 3 4)
(let* ((subs '(1 2 3 4)) ;; subs にリストを束縛
(refs subs)) ;; refs に subs のリストを束縛
(nakanuki 1 subs) ;; => '(1 3 4) ;; subs[1] を 中抜く
refs) ;; => '(1 3 4) ;; 破壊された b のリストは a と同じなので、中抜かれたリストが返る
--------------------------------------------------------------------------------
Case 2 : '(1 2 3 4) -> '(2 3 4)するテスト。
delq や delete と違って、先頭要素でもコンスを破壊する。
--------------------------------------------------------------------------------
(nakanuki 0 '(1 2 3 4)) ;; => (2 3 4)
(let* ((subs '(1 2 3 4)) ;; subs にリストを束縛
(refs subs)) ;; refs に subs のリストを束縛
(nakanuki 0 subs) ;; => '(2 3 4) ;; subs[0] を 中抜く
refs) ;; => '(2 3 4) ;; 破壊された b のリストは a と同じなので、中抜かれたリストが返る
--------------------------------------------------------------------------------
Case 3 : '(1) -> '()するテスト。
これのみコンス破壊では行なえため、同じコンスを指していた変数の値は'(nil)になる。
コンスセルを変数が指しているという原理上、この実装も delq も delete も破壊ではこれを行わない。
--------------------------------------------------------------------------------
(nakanuki 0 '(1) ) ;; => nil
(let* ((subs '(1)) ;; subs にリストを束縛
(refs subs)) ;; refs に subs のリストを束縛
(nakanuki 0 subs) ;; => nil ;; subs[0] を 中抜く
refs ;; => '(nil) ;; subs変数の変更なので refs の破壊は中途半
subs) ;; => '() ;; 2014年05月18日 18時追記
--------------------------------------------------------------------------------
Case 3 : '(1) -> '()のrefsだけどうにかならないか
マクロにする必要って、ありますかね (スコア:1)
「Case 3: '(1)→'()だけどうにかならないか」を諦めちゃうのであれば、普通に関数で書いた方がすっきりすると思います。
あと、せっかくなのでimproper-listに対応しました。こんな感じです:
Case 4: (nakanuki! 2 '(1 2 3 . 4)) => '(1 2 . 4)
Case 5: (nakanuki! 0 '(1 . 2)) => 2
Case 6: (nakanuki! 1 '#1=(1 2 3 . #1#)) => '#1=(1 3 . #1#) ;; うーん。これは今ひとつかも。
(defun nakanuki! (idx lst)
"lisからidx番目を破壊的に取り除く"
(unless (= 0 idx)
(error "idxの値(%d)がヘンですー" idx))
(cond
((atom (cdr lst))
;; ↓互換性のために入れたけど、この1行は嫌だなぁ…
(setf (car lst) (cdr lst))
(cdr lst))
((= idx 0)
(setf (car lst) (cadr lst))
(nakanuki! 1 lst))
(t
(setf (cdr (nthcdr (1- idx) lst))
(nthcdr (1+ idx) lst))
lst)))
Re:マクロにする必要って、ありますかね (スコア:1)
(unless (<=
のミスですかね?"<"はタグ開始とみなされて投稿時に消えてしまうので
マクロにしたのは「Case 3 : '(1) -> '()」を一部でも対応するためです。
(let ((subs '(1))) ;; subs にリストを束縛
(nakanuki 0 subs) ;; => nil ;; subs[0] を 中抜く
subs) ;; => nil
仕様として一定ではないのは、やはりイマイチなのですが…
そうなのですよね。
今回は、「同じリストを参照している変数があることを前提に、リスト実体に対して中抜きを行なう」という目的でした。
ですけど私用以外で使うなら「同じリストを参照している変数があること」を対応する仕様から切り捨てて、
非破壊関数で実装した方が、improper-listに対応する仕様も組み込められますし、Lispのパラダイムとも合うでしょうね。
まぁ…何故中抜き関数/マクロがネット上に落ちていないかが、分からさせられます。
Re:マクロにする必要って、ありますかね (スコア:1)
オフトピ。
(unless (<=
のミスですかね?"<"はタグ開始とみなされて投稿時に消えてしまうので
"<"といちいち書き換えるのが対処法ですね。
// …とコメントするためには"&lt;"と書くわけですが。
個人設定のオプション>コメント投稿>標準のコメント投稿モードを‘ホントのtext形式’と
変更した後でコメントすることでも解決策になるのかどうかは確かめたことない。
手っ取り早い話はhttp://books.google.co.jp/books?id=qQP5AAAAQBAJ&pg=PA92&lpg=PA... [google.co.jp]
とか一次ソースとかで確認するなりマクロ置換をする入力フォームを自前で用意するなり。。。