コード
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 件のコメント:
コメントを投稿