Hatena::Groupcadr

kozima の日記

2011-05-30

Ejection

| 22:20

わりと頑張ったのでまとめてみたくなった。

ソース: http://golf.shinh.org/reveal.rb?Ejection/kozima_1306751250&l

ループの result-form にループが入ってて読みづらくなっていますが,単に #n# を使うためです。一つの式にまとめないと参照できないのです。

平らにして,ラベルも展開してみる。

;; 入力の読み込み
(set 'a (loop while (listen) collect (read-line)))
;; 縦方向へ eject したやつを一行出力する関数
(defun f `z
  (dotimes '(length a)
    (format (< .`(count z (nth .'(apply 'map 'list 'list a)))) "~V,0T~A" .'z))
  (fresh-line))
;; 上
(dotimes (i 4) (f (- 3 i) #\U))
;; 右
(doseq (l a)
  (doseq (c l) (princ (if (eq c #\#) c " ")))
  (format t "~V@{R~}
" (count #\R l) t))
;; 下
(dotimes '3 (f .'#\D))

f は読みにくいですが二引数関数 (sys::backquote と z) で,第一引数が # で囲まれた箱から何行離れているか (0-origin),第二引数に向き (U または D) を受け取ります。

dotimes が気持ちとしては入力の各列にわたるループで,format に渡される第一引数が,eject されたときにその行の quote 列目に出てくる人の有無となります。それがあった場合には,現在の列までインデント (~V,0T) してから eject した文字を出力 (~A) します。

第一引数の中身ですが,まず (apply 'map 'list 'list a) で行と列を反転しています。で,その quote 番目を取り出しているので,要するに quote 列目を縦に拾っていったリストが nth の戻り値です。この中に z が何個現れるかを数え,結果が箱からの距離 (sys::backquote) より大きかったら,その行まで出てくると判定できます。

あと fresh-line はなんで terpri にしないのという感じですが,出力がなにもなかったときに改行が入ってしまうと困るのでしかたなく。

右に eject する部分がわりと冗長な気がするんですが,出て行った文字を空白で置き換える必要があるのでこれくらいになるみたいです。

たぶんそれほど新しいテクニックは出てきてないと思うんですが,今回は ~V,0T がうまく使えたかな,という気がします。単純に「埋まらないところは空白」にしておく方法もなくはなくて,こんなふうにもできます。

(defun f (x z)
  (format t "~A~&"
          (string-right-trim " " (apply 'map 'string
                                        (lambda (&rest l) (if (< x (count z l)) z #\ ))
                                        a))))

map を外側へもってきて,中で処理して string で返しておいて string-right-trim を使う。fresh-line も ~& で済んでしまってスマートな感じなんですが,こっちのほうが少し長くなるみたいです。