コード
vector型(いわゆる配列)は使わない方針。つまり、配列にデータを読み込んで添字でアクセスするのではなく、Emacsのバッファに記述されている文字列をそのまま処理する。したがって、"point"関連の組み込み関数を多用している。また、8近傍の要素を表す一時的なデータも、配列ではなくリストにしている。
(defun lake-count (b e)
"入力となるリージョンを選択した状態で M-x lake-count を実行すると、答えをミニバッファに出力する。"
(interactive "r")
(save-excursion
(let ((cnt 0)
(first-line (line-number-at-pos b)) ;リージョンの先頭行番号を記録しておく
(last-line (line-number-at-pos e))) ;リージョンの最終行番号を 〃
(loop for p from b to e do
(goto-char p)
(sit-for 0.1) ;カーソルの動きを観察するため一瞬停止
(when (eq ?W (char-after p))
(dfs)
(incf cnt) ))
(message "%d" cnt)))) ;結果出力
(defun dfs ()
"補助関数。現在のポイントを文字.に置換する。8近傍に文字Wがあれば再帰的に適用する。"
(let* ((cline (line-number-at-pos))
(ccol (current-column))
(poslist `((,(1- cline) ,(1- ccol)) ;8近傍を表す(行,列)のリスト
(,(1- cline) ,ccol)
(,(1- cline) ,(1+ ccol))
(,cline ,(1- ccol))
(,cline ,(1+ ccol))
(,(1+ cline) ,(1- ccol))
(,(1+ cline) ,ccol)
(,(1+ cline) ,(1+ ccol)) )))
(delete-char 1) ; この2行で W を . に置換
(insert ?.) ; 〃
(setf poslist
(remove-if ;リージョンの外に出る要素をリストから消す
(lambda (pos)
(or (< (first pos) first-line);上にはみ出す場合
(> (first pos) last-line) ;下にはみ出す場合
(< (second pos) 0))) ;左にはみ出す場合
poslist))
(mapcar (lambda (pos) ;8近傍の各要素に対してラムダ式を適用
(goto-line(first pos))
(move-to-column (second pos)) ;近傍にポイントを移動
(sit-for 0.1) ;カーソルの動きを観察するため一瞬停止
(if (eq ?W (char-after)) ;Wなら再帰的に呼び出す
(dfs)))
poslist)))
使用している組み込み関数/マクロ
詳細はEmacs上でdescribe-functionを行えば出てくるので、列挙だけしておく。
- let
- let*
- loop
- when
- incf
- eq
- sit-for
- insert
- delete-char
- mapcar
- remove-if
- char-after
- goto-line
- move-to-column
- mapcar
- save-excursion
- goto-char
- first
- second
よいか悪いかは別として、C言語などで書く場合と比べると、たくさんの語彙を駆使したプログラムになってしまう(バッククォートとか、カンマまで使っている…)。
実行&結果
- *scratch*バッファなどで、"W"と"."を使った水たまりのASCIIアートを描く。
- 先頭の行をC-SPCでマークして、カーソルを最後の行まで移動。全体を選択状態にする。
- M-x lake-count を実行する。
- 最終的にすべての"W"が"."に置換され、ミニバッファに水たまりの数が表示される。
W........WW. .WWW.....WWW ....WW...WW. .........WW. .........W.. ..W......W.. .W.W.....WW. W.W.W.....W. .W.W......W. ..W.......W.上記の入力の場合、正解は 3。動画も作ってみたが、全画面表示にしないと見えない。
0 件のコメント:
コメントを投稿