列の分割

文字列の分割を行いたいけど、cl-ppcreパッケージをその(cl-ppcre:split関数の)ためだけに使用したくはなかったので、分割関数を作成した。

;; 第一版: 平易
(defun split (delim seq &aux (len (length delim)))
  (declare (unmuffle-conditions compiler-note))
  (when (zerop len)
    (return-from split (list seq))) ; これが無いと、delim=空列、の際に無限ループとなる

  (loop FOR beg = 0 THEN (+ end len)
        FOR end = (search delim seq :start2 beg)
        COLLECT (subseq seq beg end)
        WHILE end))

;; 第二版: 少し高機能: 開始位置、終端位置、分割個数が指定可能
(defun split (delim seq &key (start 0) end limit)
  (when (and (numberp limit)
             (not (plusp limit)))
    (return-from split '()))

  (when (zerop len)
    (return-from split (list seq)))

  (loop WITH len = (length delim)
        FOR beg~ = start THEN (+ end~ len)
        FOR end~ = (if (and limit (zerop (decf limit)))
                       nil
                     (search delim seq :start2 beg~ :end2 end))
        COLLECT (subseq seq beg~ (or end~ end))
        WHILE end~))

上の関数は、文字列に限らず列一般に対して適用できるという(cl-ppcre:split関数に比べて些細な)利点がある。

> (split "ab" "123abc")
--> ("123" "c")

> (split "a" '(1 2 3 #\a #\b c))
--> ((1 2 3) (#\b C))

> (split "a" "aaaaaaaaa")  ; 区切り文字が連接していると空列となる
--> ("" "" "" "" "" "" "" "" "" "")

> (split "a" "aaaaaaaaa" :limit 3) ; 要素数を三個までに限定
--> ("" "" "aaaaaaa")

> (split "ab" "123abcd" :start 1)  ; 開始位置指定: (split "ab" (subseq "123abcd" 1))と等しい
--> ("23" "cd")

最近は、LOOPマクロを多用するようになった。
以前は、再帰関数で頑張っていたのに...。