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