nekoTheShadow’s diary

技術ブログとして始めたはずが、読書&愚痴ブログになりました(´・ω・`)

Schemeでsplit!

 指定されたデリミタを区切り文字として文字列を分割、リストに格納する関数――いわゆるsplit関数が入用になることがあり、しこしこ書きました。せっかくなのでブログにはりつけておきます。

; 第2引数の文字集合を区切り文字として、第1引数の文字列を区切り、リストに格納する
(define (string-split string char-set-delimiter)
    (let loop((str (string->list string)) (unit '()) (result '()))
        (if (null? str)
            (reverse (map (lambda (ls) (list->string (reverse ls))) (cons unit result)))
            (if (char-set-contains? char-set-delimiter (car str))
                (loop (cdr str) '() (cons unit result))
                (loop (cdr str) (cons (car str) unit) result)))))

(string-split "1234/abc\nあいう" (string->char-set "/\n")) ; => ("1234" "abc" "あいう")

 ただ文字列を分解するにあたっては多くの場合、SRFI-13に定義されているstring-tokenizeでこと足りると思います。今回はどうしても必要だったというか、split関数がないとあまりに不便だったので、自分で実装しましたが、そうでもないときは「すでにあるもの」を使ったほうがいいですね。

(string-tokenize "123\s456\n789" char-set:digit) ; => ("123" "456" "789")
(string-tokenize "abc,def/ghi" char-set:letter)  ; => ("abc" "def" "ghi")

 string-tokenizeはsplitの逆、つまり指定した文字集合以外の文字を区切り文字とみなして文字列を分割するというほかの言語ではあまり見ない仕様になっているので、最初は随分とまどったのですが、慣れてしまうとそうでもないですね。

 あと今でも忘れがちなのは、string-tokenizeの第2引数にとる文字集合について。文字集合を生成する際いちいちstring->char-setやlist->char-setを使うのは面倒なので、char-set:digitやchar-set:letterのような「定義済みの文字集合」をよく使うのですが――この「定義済みの文字集合」が定義されているのはSRFI-14です。つまりstring-tokenizeが定義されているSRFI-13とは別であり、うっかりライブラリをインクルードし忘れるなんてことはないようにしましょう。わたしはやらかして1時間ほど無駄にした経験があります……(´・ω・`)