文字列をリストで与えられた長さに区切る

例えば Ruby で書くとこういうやつ。

def split_with(str, lis)
  a = Array.new(lis.size, "A")
  tmpl = a.zip(lis).map{|x, y| x + y.to_s }.join("")
  str.unpack(tmpl)
end

p split_with("abcdefghij", [1, 2, 3, 4])
takatoh@nightschool $ ruby split_with.rb
["a", "bc", "def", "ghij"]

Scheme ではどうだろう。

フツウの再帰版

(define split-with
  (lambda (str lis)
    (let loop ((sl (string->list str)) (l lis))
      (cond
        ((null? l) '())
        (else (cons (list->string (take sl (car l)))
          (loop (drop sl (car l)) (cdr l))))))))

(print (split-with "abcdefghij" '(1 2 3 4)))

なんの工夫もない。

takatoh@nightschool $ gosh split-with.scm
(a bc def ghij)

末尾再帰版

ちょっと工夫して末尾再帰。

(define split-with
  (lambda (str lis)
    (let loop ((sl (string->list str)) (l1 lis) (l2 '()))
      (cond
        ((null? l1) (reverse l2))
        (else (loop (drop sl (car l1))
                (cdr l1)
                (cons (list->string (take sl (car l1))) l2)))))))

(print (split-with "abcdefghij" '(1 2 3 4)))
takatoh@nightschool $ gosh split-with2.scm
(a bc def ghij)

map-accum版

もう少し何かないかと探したら、map-accum があった。

(use gauche.collection)

(define split-with
  (lambda (str lis)
    (let ((f (lambda (n sl) (values (take sl n) (drop sl n)))))
      (receive (l s) (map-accum f (string->list str) lis)
      (map list->string l)))))

(print (split-with "abcdefghij" '(1 2 3 4)))

これはちょっと説明をしておく。split-with の中で let を使って手続き f を定義している。これは、リストの各要素に適用される手続きで、2つの引数をとって 2つの値を返す。多値ってやつだな。覚えてるぞ。values で2つの値を返せばいい。1つ目は map の様に集められて、もう1つは次の適用の引数になる。で、map-accum 自体も2つの値を返す。1つ目は f の1つ目の返り値を集めたリストで、もう1つは最後の適用の2つ目の返り値だ。値が2つ返ってくるので、receive を使って l と s で受けている。s の方は使ってないけどね。最後に、l は文字のリストのリストになているので、文字列のリストに変換して完了。

takatoh@nightschool $ gosh split-with3.scm
(a bc def ghij)

実行時間の比較

せっかくなので比較してみた。

takatoh@nightschool $ time gosh split-with.scm
(a bc def ghij)

real	0m0.012s
user	0m0.011s
sys	0m0.000s
takatoh@nightschool $ time gosh split-with2.scm
(a bc def ghij)

real	0m0.015s
user	0m0.009s
sys	0m0.005s
takatoh@nightschool $ time gosh split-with3.scm
(a bc def ghij)

real	0m0.023s
user	0m0.023s
sys	0m0.000s

再帰版と末尾再帰版はあんまり変わらないけど、map-accum 版は明らかに遅い。そういうもんか。