2007-06-07
■ リストをカンマ区切りで出力する
問題
文字列のリストをカンマ区切りで出力したい。
答え
Perlだとprint join(", ", @list)と書くところですよね。Gaucheでも同じように、リストを", "で連結した文字列を構築して出力することが可能です。
gosh> (print (string-join '("evian" "contrex" "crystal geyser") ", ")) evian, contrex, crystal geyser
上の例のように対象の文字列が短いときには最も効率的で、かつ理解しやすい書き方です。しかしこの方法はすぐに捨てられる文字列を作成するので、リストに含まれる文字列が巨大なときには効率が悪くなります。たとえばファイル全体で1つの文字列になっているときなどには、連結を避けたいと思うでしょう。文字列を連結を避ける方法をいくつか紹介します。
まず最初に紹介するのは、リストの要素間にカンマを挿入した新たなリストを作成し、そのリストの各要素をdisplayで出力する、という方法です。リストに要素を挿入するのはintersperseでできます。
(use util.list) ; intersperse (for-each display (intersperse ", " '("a" "b" "c"))) ;; "a, b, c"を出力
intersperseで構築されたリストは最終的な結果には現れず、すぐ捨てられてしまいますが、このような一時的なリストの構築さえせずに済ますことは可能でしょうか? これはLingrで議論されて、方法がいくつか提案されました。次の3つのコードはどちらも「a,b,c」を出力します。
まず一つめは、最初の一度だけセパレータの代わりに空文字列を渡す方法。
(fold (lambda (s d) (display d) (display s) #\,) "" '("a" "b" "c"))
これはreduceを副作用的に使って最初の一度だけを特別扱いする方法。
(use srfi-1) (reduce (lambda (a b) (and b (display b)) (write-char #\,) (display a) #f) #f '("a" "b" "c"))
そして最後に、for-eachが複数のリストを取れることを利用して、表示を行う手続きのリストを渡す方法。表示手続きのリストは無限リストなのですが、無限リストの最初の要素だけはカンマを出力しない手続きになっています。
(use srfi-1) (for-each (cut <> <>) (cons display (circular-list (lambda (e) (display ",") (display e)))) '("a" "b" "c"))
上の3案はどれもトリッキーですが、それを補うほどの効率向上があるわけではないでしょう。普通の場面ではオブジェクトのアロケーションをさほど気にしないのがScheme的なプログラミングスタイルですから、特別な理由がなければstring-joinを、文字列が巨大なときにはintersperseの使用をお勧めします。
参照
- リファレンスマニュアル: display、intersperse、for-each、fold、reduce、circular-list
- Lingrの2006-12-21のログ
