不確定特異点

広く深く、ところどころ超深く

2つのシーケンスをインターリーブする

例えば、以下のように 2 つのリストの要素を互い違いに組み替えたリストを作りたいとします。

(setf s1 '(1 2 3))
(setf s2 '(a b c))

(interleave s1 s2) ;=> (1 a 2 b 3 c)

Common Lisp で書くとしたらどう書くのがシンプルなんだろう?専用の関数を定義せずに、既存関数の組み合わせで書いてみました。

解 1

(apply #'append (mapcar #'list s1 s2))

;; alexandria 版(同上)
(alexandria:mappend #'list s1 s2) 

mapcar で s1, s2 の各要素のリストからなるリストを作って、それらをまとめて append する方法。alexandria の mappend 版も最初のものとほぼ等価です。mapcar の仕様から、s1, s2 の長さが異なる場合は短い方で打ち切られます。

解 2

(alexandria:flatten (mapcar #'cons s1 s2))

mapcar で s1, s2 の各要素のドット対からなるリストを作って、それらを木と見なして flatten する方法。解 1 と似てますが、mapcar の結果がリストのリストではなく、ドット対のリストになるのでコンスセルは少ないです。ただし、flatten しちゃうので s1, s2 の要素が consp な要素だと結果が変わってしまいます。そういう意味だとあまり良くないかもしれません。

解 3?

(merge 'list (copy-list s1) (copy-list s2)
       (let ((toggle t))
         #'(lambda (a b)
             (declare (ignore a b))
             (setf toggle (not toggle)))))

merge 関数は第 4 引数に渡した述語関数の真偽値により 2 つの列を順序づけてマージする関数なので、その述語関数として、呼ばれるたびに真偽値を反転して返すクロージャを渡す方法。'list を 'string にして、copy-list を copy-seq に変更すれば文字列にも対応できるとか、s1, s2 の長さが異なった場合でも途中で打ち切られることなくすべての要素がマージされるとかの利点はあるかなーとは思ったものの、merge 関数の内部で述語関数がどのように使われているかは実装依存でわからない以上、正解とは言えない気がします。

(2015/8/31 追記)

解 4

(mapcan #'list s1 s2)

Reddit lisp_ja で g000001 さんに教えて頂きました。これがベストですね。

www.reddit.com