例えば 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 版は明らかに遅い。そういうもんか。