[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
座標軸が表示されていると、グラフの理解に役立つ。 これらは尺度を与えてくれる。 前節(see 節 15. グラフの準備)では、 グラフの本体を表示するコードを書いた。 ここでは、本体自身に加えて、ラベルを付けた垂直軸と水平軸も 表示するコードを書く。
バッファへの挿入はポイントの右下へ向かって行われるので、 グラフを描く新しい関数では、まず、Y軸、つまり、垂直軸を描いてから、 グラフの本体を描き、最後に、X軸、つまり、水平軸を描く。 この描く順序から、関数の内容が決まる。
最終的なグラフはつぎのようになる。
10 - * * * * ** * *** 5 - * ******* * *** ******* ************* *************** 1 - **************** | | | | 1 5 10 15 |
このグラフでは、垂直と水平の両者の軸に、数字のラベルを付けてある。 しかし、ある種のグラフでは、水平軸は時間であり、つぎのように、 月名のラベルのほうが適することもある。
5 - * * ** * ******* ********** ** 1 - ************** | ^ | Jan June Jan |
もちろん、少々考えれば、垂直/水平軸のさまざまなラベリング方法を考えつく。 われわれの仕事も複雑になる。 複雑さは混乱を招く。 したがって、まずは単純なラベリング方法を選び、 そのあとで修正なり置き換えを行う。
以上の考察から、関数print-graph
の概略はつぎのようになる。
(defun print-graph (numbers-list) "説明文..." (let ((height ... ...)) (print-Y-axis height ... ) (graph-body-print numbers-list) (print-X-axis ... ))) |
print-graph
の関数定義の各部分を順番に扱おう。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
print-graph
の変数リストprint-graph
の変数リスト"へのコメント(無し)
関数print-graph
を書く場合、最初の仕事は、 let
式の変数リストを書くことである (関数を対話的にしたり、説明文字列の内容については、 しばらく手をつけないでおく)。
変数リストでは、数個の変数を設定する。 明らかに、垂直軸のラベルの先頭は少なくともグラフの高さである必要があるので、 この情報をここで得ておく必要がある。 関数print-graph-body
もこの情報を必要とすることに注意してほしい。 異なる2つの場所でグラフの高さを計算する理由はないので、 ここでの計算を利用するようにprint-graph-body
の以前の定義を変更する。
同様に、X軸を描く関数と関数print-graph-body
の両者は、 各シンボルの幅を知る必要がある。 この計算もここで行い、print-graph-body
の以前の定義を変更する。
水平軸のラベルの長さは少なくともグラフの幅である必要がある。 しかし、この情報は水平軸を描く関数でのみ使われるので、 ここで計算する必要はない。
以上の考察から、print-graph
のlet
の変数リストはつぎのようになる。
(let ((height (apply 'max numbers-list)) ; 第1版 (symbol-width (length graph-blank))) |
あとでわかるように、この式は実は正確ではない。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
print-Y-axis
print-Y-axis
"へのコメント(無し)
関数print-Y-axis
の仕事は、つぎのような垂直軸のラベルを描くことである。
10 - 5 - 1 - |
関数にはグラフの高さが渡され、適切な数字や目盛を作成して挿入する。
Y軸のラベルをどのようにすべきかは図からは簡単にわかるが、 言葉で書き表したり、そのようにする関数定義を書くことはまた別である。 5行ごとに数字や目盛が必要なのではない。 `1'と`5'のあいだには3行(2行目、3行目、4行目)あるが、 `5'と`10'のあいだには4行(6行目、7行目、8行目、9行目)ある。 基準行(数1)には数字と目盛が必要であり、最下行から5行目および5の倍数の行に 数字と目盛が必要であるといい直したほうがよい。
つぎの問題は、ラベルの高さをどうすべきかである。 グラフのもっとも大きな高さが7であったとしよう。 Y軸のもっとも大きなラベルを`5 -'にして、 グラフの棒がラベルより高くなってもよいのだろうか? あるいは、もっとも大きなラベルを`7 -'にして、 グラフの最大値の目印にするのだろうか? あるいは、もっとも大きなラベルを5の倍数の`10 -'にして、 グラフの最大値よりも高くするのだろうか?
後者が望ましい。 ほとんどのグラフは、刻み幅の倍数の長さの辺の四角形の中に書かれる。 刻み幅が5であれば、辺の長さは、5、10、15、などなどである。 垂直軸の高さを刻み幅の倍数にしようとすると、 変数リストの高さを計算する単純な式はまちがっていることに気づく。 式は(apply 'max numbers-list)
であった。 これは、正確に最大の高さを返し、 5の倍数となるような最大数に繰り上げた値を返さない。 より複雑な式が必要である。
このような場合の常として、小さな複数の問題に分解できれば、 複雑な問題は簡単になる。
まず、グラフの最大の高さが5の倍数、つまり5、10、15、などの場合を考える。 この場合には、この値をY軸の高さとして使える。
数が5の倍数であるかを調べる比較的簡単な方法は、 数を5で割って余りを調べることである。 余りがなければ、数は5の倍数である。 つまり、7を5で割ると余りは2となり、7は5の倍数ではない。 いい方を変えて教室でのことを思い出すと、5を1倍して余り2を足すと7になる。 しかし、5を2倍して余り0を足すと10になり、10は5の倍数である。
C.2.1 余りの計算 How to compute the remainder of a division. C.2.2 Y軸の要素の作成 Construct a line for the Y axis. C.2.3 Y軸のコラムの作成 Generate a list of Y axis labels. C.2.4 print-Y-axis
の最終版Print a vertical axis, final version.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
Lispでは、余りを計算する関数は%
である。 この関数は、第1引数を第2引数で割った余りを返す。 %
は、apropos
を使って探せないEmacs Lispの関数である。 M-x apropos RET remainder RETとタイプしても何も得られない。 %
の存在を知る唯一の方法は、本書のような書籍を読むか、 Emacs Lispのソースを読むことである。 関数%
は、付録で説明しているrotate-yank-pointer
のコードで 使われている(See 節 B.1.1 rotate-yank-pointer
の本体)。
つぎの2つの式を評価すれば関数%
を試せる。
(% 7 5) (% 10 5) |
最初の式は2を返し、2番目は0を返す。
返された値が0かどうかを調べるには、関数zerop
を使う。 この関数は、数である引数が0ならばt
を返す。
(zerop (% 7 5)) => nil (zerop (% 10 5)) => t |
したがって、つぎの式はグラフの高さが5の倍数ならばt
を返す。
(zerop (% height 5)) |
(もちろん、height
の値は、(apply 'max numbers-list)
の値である。)
一方、height
の値が5の倍数でなかったら、 それより大きなつぎの5の倍数に直したいのである。 これは、すでに馴染みのある関数を使った単純な算術でできる。 まず、height
の値を5で割って、5の何倍かを調べる。 したがって、12は、5の2倍はある。 この商に1を加えて5を掛けると、高さより大きなつぎの5の倍数の値を得られる。 12は、5の2倍はある。 2に1を加えて、5を掛ける。 結果は15であり、これは12より大きなつぎの5の倍数である。 これに対応するLispの式はつぎのとおりである。
(* (1+ (/ height 5)) 5) |
たとえば、つぎの式を評価すると結果は15である。
(* (1+ (/ 12 5)) 5) |
これまでの説明では、Y軸の刻み幅として5を使ってきたが、 これ以外の値を使いたい場合もあろう。 一般的には、5のかわりに、値を設定できる変数を使うべきである。 この変数の名前としてはY-axis-label-spacing
が最適であると思う。 これを使うと、if
式はつぎのようになる。
(if (zerop (% height Y-axis-label-spacing)) height ;; そうでなければ (* (1+ (/ height Y-axis-label-spacing)) Y-axis-label-spacing)) |
この式は、高さがY-axis-label-spacing
の値の倍数ならばheight
の 値を返し、そうでなければ、 Y-axis-label-spacing
のつぎに大きな倍数を計算してその値を返す。
(Y-axis-label-spacing
の値を設定してから) この式を関数print-graph
のlet
式に埋め込む。
(defvar Y-axis-label-spacing 5 "Number of lines from one Y axis label to next.") ... (let* ((height (apply 'max numbers-list)) (height-of-top-line (if (zerop (% height Y-axis-label-spacing)) height ;; そうでなければ (* (1+ (/ height Y-axis-label-spacing)) Y-axis-label-spacing))) (symbol-width (length graph-blank)))) ... |
(関数let*
を使っていることに注意してほしい。 高さの初期値を、いったん、式(apply 'max numbers-list)
で計算し、 最終的な値を計算するためにheight
の値を使っている。 let*
について詳しくは、See 節 let*
式。)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
垂直軸を書くときには、`5 -'や`10 - 'などの文字列を 5行ごとに書きたい。 さらに、数字と目盛をきちんとそろえたいので、 短い数字には空白をまえに埋める必要がある。 数字を2桁で表す文字列がある場合には、 数字が1桁になる文字列では、数字の直前に空白文字を埋める必要がある。
数の桁数を調べるには、関数length
を使う。 しかし、関数length
は文字列のみを扱い、数を扱えない。 そのため、数を文字列に変換する必要がある。 これは関数int-to-string
で行う。 たとえば、
(length (int-to-string 35)) => 2 (length (int-to-string 100)) => 3 |
さらに、各ラベルの数字のあとには` - 'などの文字列が続く。 これをY-axis-tic
と呼ぶことにする。 この変数をつぎのようにdefvar
で定義する。
(defvar Y-axis-tic " - " "String that follows number in a Y axis label.") |
Y軸のラベルの長さは、Y軸の目盛の長さとグラフの先頭の数字の桁数の総和である。
(length (concat (int-to-string height) Y-axis-tic))) |
この値は、関数print-graph
の変数リストでfull-Y-label-width
として 計算する (始めは、これを変数リストに含めるとは思っていなかった)。
垂直軸の完全なラベルを作るには、数字に目盛を繋げ、数字の桁数に応じて それらのまえに空白文字を繋げる。 ラベルは3つの部分、(省略されるかもしれない)空白、数字、目盛から成る。 関数には、特定の行の数の値と、print-graph
で(一度だけ)計算された 先頭行の幅が渡される。
(defun Y-axis-element (number full-Y-label-width) "Construct a NUMBERed label element. A numbered element looks like this ` 5 - ', and is padded as needed so all line up with the element for the largest number." (let* ((leading-spaces (- full-Y-label-width (length (concat (int-to-string number) Y-axis-tic))))) (concat (make-string leading-spaces ? ) (int-to-string number) Y-axis-tic))) |
関数Y-axis-element
は、必要ならばまえに置く空白、 文字列にした数字、目盛を繋げる。
ラベルに何個の空白が必要かを調べるために、目的とするラベルの幅から、 数字の桁数と目盛の長さを足した実際の目盛の長さを引く。
関数make-string
を使って、空白文字を挿入する。 この関数は2つの引数を取る。 第1引数で文字列の長さを指定し、 第2引数には挿入する文字を特別な形式で指定する。 ここでは、`? 'のように疑問符のあとに空白文字を続ける。 文字の書き方に関する記述は、 See 節 `Character Type' in
関数int-to-string
は、文字列を繋げる式で使われており、 数を文字列に変換する。 この文字列には、まえに置く空白文字や目盛が繋げられる。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
これまでの関数は、 垂直軸のラベルとして挿入する数字や空白から成る文字列のリストを 生成する関数を作るために必要なすべての道具を提供する。
(defun Y-axis-column (height width-of-label) "Construct list of Y axis labels and blank strings. For HEIGHT of line above base and WIDTH-OF-LABEL." (let (Y-axis) (while (> height 1) (if (zerop (% height Y-axis-label-spacing)) ;; ラベルを挿入する (setq Y-axis (cons (Y-axis-element height width-of-label) Y-axis)) ;; そうでなければ、空白を挿入する (setq Y-axis (cons (make-string width-of-label ? ) Y-axis))) (setq height (1- height))) ;; 基準行を挿入する (setq Y-axis (cons (Y-axis-element 1 width-of-label) Y-axis)) (nreverse Y-axis))) |
この関数では、height
の値から始めて1ずつ減らす。 引き算をするたびに、値がY-axis-label-spacing
の倍数かどうかを調べる。 倍数ならば、関数Y-axis-element
を使って数字を書いたラベルを作る。 そうでなければ、関数make-string
を使って空白ラベルを作る。 基準行では、数字1のあとに目盛を付ける。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
print-Y-axis
の最終版print-Y-axis
の最終版"へのコメント(無し)
関数Y-axis-column
が作成したリストは関数print-Y-axis
に渡され、 リストをコラムとして挿入する。
(defun print-Y-axis (height full-Y-label-width &optional vertical-step) "Insert Y axis using HEIGHT and FULL-Y-LABEL-WIDTH. Height must be the maximum height of the graph. Full width is the width of the highest label element. Optionally, print according to VERTICAL-STEP." ;; Value of height and full-Y-label-width ;; are passed by `print-graph'. (let ((start (point))) (insert-rectangle (Y-axis-column height full-Y-label-width vertical-step)) ;; グラフを挿入できるようにポイントを移動 (goto-char start) ;; ポイントを full-Y-label-width だけ進める (forward-char full-Y-label-width))) |
print-Y-axis
は、関数insert-rectangle
を使って 関数Y-axis-column
が作成したY軸のラベルを挿入する。 さらに、グラフ本体を書けるようにポイントを正しい位置に移動する。
つぎのようにしてprint-Y-axis
を試せる。
Y-axis-label-spacing Y-axis-tic Y-axis-element Y-axis-columnprint-Y-axis |
(print-Y-axis 12 5) |
eval-expression
)とタイプする。
yank
)で、graph-body-print
式を ミニバッファにヤンクする。
Emacsは、先頭が`10 - 'であるようなラベルを垂直に表示する (関数print-graph
はheight-of-top-line
の値を渡し、 その値は15になる)。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
print-X-axis
print-X-axis
"へのコメント(無し)
X軸のラベルはY軸のラベルに似ているが、目盛は数字の行の上にある。 ラベルはつぎのようである。
| | | | 1 5 10 15 |
最初の目盛は、グラフの最初のコラムの下にあり、 そのまえには複数の空白文字がある。 これらの空白は、その上にあるY軸のラベルの幅に相当する。 2番目、3番目、4番目、それ以降の目盛はすべて等間隔で、 X-axis-label-spacing
の値で決まる。
X軸の第2行目は、空白をまえに置いた数字で、 変数X-axis-label-spacing
の値で決まる間隔だけ離れている。
グラフ本体の表示に使うシンボルの幅を変更してもラベルの付き方が 変わらないように、変数X-axis-label-spacing
の値は、 symbol-width
を単位とすべきである。
関数print-X-axis
は、関数print-Y-axis
とほぼ同様に作れるが、 X軸は目盛の行と数字の行の2行であることが異なる。 それぞれを表示する別々の関数を書いて、 それらを関数print-X-axis
で組み合わせる。
これは、3段階の処理になる。
print-X-axis-tic-line
を書く。
print-X-axis-numbered-line
を書く。
print-X-axis-tic-line
とprint-X-axis-numbered-line
を使って、 軸の2つの行を描く関数print-X-axis
を書く。
C.3.1 X軸の目盛 Create tic marks for the horizontal axis.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
最初の関数は、X軸の目盛を描く。 目盛自体とその間隔を指定する必要がある。
(defvar X-axis-label-spacing (if (boundp 'graph-blank) (* 5 (length graph-blank)) 5) "Number of units from one X axis label to next.") |
(graph-blank
の値は、別のdefvar
で設定される。 述語boundp
は、graph-blank
に値が設定されたかどうかを調べる。 設定されていない場合には、boundp
はnil
を返す。 graph-blank
が束縛されていない場合に、この条件式を使わないと、 エラーメッセージ`Symbol's value as variable is void'を得る。)
(defvar X-axis-tic-symbol "|" "String to insert to point to a column in X axis.") |
目標はつぎのような行を作ることである。
| | | | |
最初の目盛は、最初のコラムの下にくるように字下げされるが、 これは、Y軸のラベル幅と同じである。
目盛の要素は、目盛からつぎの目盛までのあいだの空白文字と、 目盛のシンボルである。 空白の個数は、目盛のシンボルの幅とX-axis-label-spacing
で決まる。
コードはつぎのようになる。
;;; X-axis-tic-element ... (concat (make-string ;; 空白文字の文字列を作る (- (* symbol-width X-axis-label-spacing) (length X-axis-tic-symbol)) ? ) ;; 空白文字列に目盛のシンボルを繋ぐ X-axis-tic-symbol) ... |
つぎは、最初の目盛をグラフの最初のコラムに字下げするために必要な空白文字の 個数を決めることである。 関数print-graph
がfull-Y-label-width
として渡した値を使う。
X-axis-leading-spaces
を計算するコードはつぎのとおりである。
;; X-axis-leading-spaces ... (make-string full-Y-label-width ? ) ... |
水平軸の長さを決める必要もある。 この長さは、数のリストの長さと水平軸の目盛の個数で決まる。
;; X-length ... (length numbers-list) ;; tic-width ... (* symbol-width X-axis-label-spacing) ;; number-of-X-tics (if (zerop (% (X-length tic-width))) (/ (X-length tic-width)) (1+ (/ (X-length tic-width)))) |
以上のことから、X軸の目盛の行を描く関数を書ける。
(defun print-X-axis-tic-line (number-of-X-tics X-axis-leading-spaces X-axis-tic-element) "Print tics for X axis." (insert X-axis-leading-spaces) (insert X-axis-tic-symbol) ; 最初のコラムの真下 ;; 2番目の目盛を正しい位置に挿入する (insert (concat (make-string (- (* symbol-width X-axis-label-spacing) ;; Insert white space up to second tic symbol. (* 2 (length X-axis-tic-symbol))) ? ) X-axis-tic-symbol)) ;; 残りの目盛を挿入する (while (> number-of-X-tics 1) (insert X-axis-tic-element) (setq number-of-X-tics (1- number-of-X-tics)))) |
数字の行も同じように簡単である。
まず、空白をまえに置いた数字を作る。
(defun X-axis-element (number) "Construct a numbered X axis element." (let ((leading-spaces (- (* symbol-width X-axis-label-spacing) (length (int-to-string number))))) (concat (make-string leading-spaces ? ) (int-to-string number)))) |
つぎに、最初のコラムの直下に描く「1」から始まる数字の行を書く関数を作る。
(defun print-X-axis-numbered-line (number-of-X-tics X-axis-leading-spaces) "Print line of X-axis numbers" (let ((number X-axis-label-spacing)) (insert X-axis-leading-spaces) (insert "1") (insert (concat (make-string ;; つぎの数字までの空白文字を挿入する (- (* symbol-width X-axis-label-spacing) 2) ? ) (int-to-string number))) ;; 残りの数字を挿入する (setq number (+ number X-axis-label-spacing)) (while (> number-of-X-tics 1) (insert (X-axis-element number)) (setq number (+ number X-axis-label-spacing)) (setq number-of-X-tics (1- number-of-X-tics))))) |
最後に、print-X-axis-tic-line
とprint-X-axis-numbered-line
を 使うprint-X-axis
を書く必要がある。
関数では、print-X-axis-tic-line
とprint-X-axis-numbered-line
が 使うローカル変数の値を決定する必要があり、 そのあと、これらの関数を呼び出す。 さらに、2つの行を分ける復帰を書く必要もある。
関数は、5つのローカル変数を指定する変数リストと、 2つの行のおのおのを描く関数の呼び出しから成る。
(defun print-X-axis (numbers-list) "Print X axis labels to length of NUMBERS-LIST." (let* ((leading-spaces (make-string full-Y-label-width ? )) ;; symbol-width は graph-body-print が与える (tic-width (* symbol-width X-axis-label-spacing)) (X-length (length numbers-list)) (X-tic (concat (make-string ;; 空白の文字列を作る (- (* symbol-width X-axis-label-spacing) (length X-axis-tic-symbol)) ? ) ;; 空白を目盛のシンボルに繋ぐ X-axis-tic-symbol)) (tic-number (if (zerop (% X-length tic-width)) (/ X-length tic-width) (1+ (/ X-length tic-width))))) (print-X-axis-tic-line tic-number leading-spaces X-tic) (insert "\n") (print-X-axis-numbered-line tic-number leading-spaces))) |
print-X-axis
を試してみよう。
X-axis-tic-symbol
、X-axis-label-spacing
、 print-X-axis-tic-line
とともに X-axis-element
、print-X-axis-numbered-line
、 print-X-axis
をインストールする。
(progn (let ((full-Y-label-width 5) (symbol-width 1)) (print-X-axis '(1 2 3 4 5 6 7 8 9 15 16)))) |
eval-expression
)とタイプする。
yank
)でテスト用の式をミニバッファにヤンクする。
Emacsはつぎのように水平軸を表示する。
| | | | | 1 5 10 15 20 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
グラフ全体を表示する準備ができた。
正しいラベルを付けたグラフを描く関数は、 まえに作った概略(see 節 C. ラベル付きグラフ) とほぼ同じであるが多少追加点もある。
つぎに概略を示す。
(defun print-graph (numbers-list) "説明文..." (let ((height ... ...)) (print-Y-axis height ... ) (graph-body-print numbers-list) (print-X-axis ... ))) |
最終版は、計画したものと2つの点で異なる。 第一に、変数リストには一度だけ計算する値が追加してある。 第二に、ラベルの行間隔を指定するオプションを持つことである。 後者の機能は本質的であり、これがないと、1画面や1ページに収まらない グラフができてしまう。
この新しい機能のためには、vertical-step
を追加するように 関数Y-axis-column
を変更する必要がある。 関数はつぎのようになる。
;;; 最終版 (defun Y-axis-column (height width-of-label &optional vertical-step) "Construct list of labels for Y axis. HEIGHT is maximum height of graph. WIDTH-OF-LABEL is maximum width of label. VERTICAL-STEP, an option, is a positive integer that specifies how much a Y axis label increments for each line. For example, a step of 5 means that each line is five units of the graph." (let (Y-axis (number-per-line (or vertical-step 1))) (while (> height 1) (if (zerop (% height Y-axis-label-spacing)) ;; ラベルを挿入する (setq Y-axis (cons (Y-axis-element (* height number-per-line) width-of-label) Y-axis)) ;; そうでなければ、空白を挿入する (setq Y-axis (cons (make-string width-of-label ? ) Y-axis))) (setq height (1- height))) ;; 基準行を挿入する (setq Y-axis (cons (Y-axis-element (or vertical-step 1) width-of-label) Y-axis)) (nreverse Y-axis))) |
グラフの最大の高さとシンボルの幅は、print-graph
のlet
式で 計算される。 そこで、graph-body-print
がこれらの値を受け取るように変更する必要がある。
;;; 最終版 (defun graph-body-print (numbers-list height symbol-width) "Print a bar graph of the NUMBERS-LIST. The numbers-list consists of the Y-axis values. HEIGHT is maximum height of graph. SYMBOL-WIDTH is number of each column." (let (from-position) (while numbers-list (setq from-position (point)) (insert-rectangle (column-of-graph height (car numbers-list))) (goto-char from-position) (forward-char symbol-width) ;; コラムごとにグラフを描く (sit-for 0) (setq numbers-list (cdr numbers-list))) ;; X軸のラベル用に、ポイントを移動する (forward-line height) (insert "\n"))) |
最後に、関数print-graph
のコードを示す。
;;; 最終版
(defun print-graph
(numbers-list &optional vertical-step)
"Print labelled bar graph of the NUMBERS-LIST.
The numbers-list consists of the Y-axis values.
Optionally, VERTICAL-STEP, a positive integer,
specifies how much a Y axis label increments for
each line. For example, a step of 5 means that
each row is five units."
(let* ((symbol-width (length graph-blank))
;;
|
C.4.1 print-graph
のテストRun a short test. C.4.2 単語やシンボルの個数のグラフ Executing the final code. C.4.3 グラフ The graph itself!
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
print-graph
のテストprint-graph
のテスト"へのコメント(無し)
関数print-graph
を数の短いリストで試してみよう。
Y-axis-column
、graph-body-print
、print-graph
(および、残りのコード)をインストールする。
(print-graph '(3 2 5 6 7 5 3 4 6 4 3 2 1)) |
eval-expression
)とタイプする。
yank
)で、ミニバッファにテスト用の式をヤンクする。
Emacsはつぎのようなグラフを表示する。
10 - * ** * 5 - **** * **** *** * ********* ************ 1 - ************* | | | | 1 5 10 15 |
一方、vertical-step
の値として2をprint-graph
に渡す つぎの式を評価すると、
(print-graph '(3 2 5 6 7 5 3 4 6 4 3 2 1) 2) |
グラフはつぎのようになる。
20 - * ** * 10 - **** * **** *** * ********* ************ 2 - ************* | | | | 1 5 10 15 |
(垂直軸の最後が「2」であるのは、バグであろうか機能であろうか? バグと考えるのであれば、かわりに「1」(あるいは、「0」)を表示するように ソースを直せばよい。)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
グラフ作成に必要なコードはすべて書いた。 単語やシンボルの個数が10個未満の関数定義はいくつ、 10から19個のものはいくつ、20から29個のものはいくつ、 などなどを示すグラフである。
これは、多段の処理である。 まず、必要なコードをすべてロードしてあることを確認する。
top-of-ranges
の値を変えてしまった場合に備えて、 top-of-ranges
の値を再設定しておこう。 つぎの式を評価する。
(setq top-of-ranges '(10 20 30 40 50 60 70 80 90 100 ) |
つぎは、各範囲に属する単語やシンボルの個数のリストを作る。
つぎの式を評価する。
(setq list-for-graph (defuns-per-range (sort (recursive-lengths-list-many-files (directory-files "/usr/local/emacs/lisp" t ".+el$")) '<) top-of-ranges)) |
筆者のマシンでは、1時間ほど掛かる。 Emacs第19.23版の303個のLispのファイルを調べる。 処理が完了すると、list-for-graph
にはつぎのような値が入る。
(537 1 120 116 99 13 220) |
つまり、筆者の場合、単語やシンボルの個数が10個未満の関数定義は537、 10から19個のものは1027、20から29個のものは955、などなどである。
明らかに、このリストを見るだけで、 ほとんどの関数定義では10から30個の単語やシンボルが含まれことがわかる。
ではグラフを描こう。 しかし、1030行もの高さのグラフを描きたいわけではない。 高さが25行よりも低いグラフを描きたい。 画面や1ページの紙に簡単に収まるような高さのグラフである。
これには、list-for-graph
の各値を1/50に減らす必要がある。
まだ説明していない2つの関数mapcar
とlambda
を使うと、 つぎの小さな関数でできる。
(defun one-fiftieth (full-range) "Return list, each number one-fiftieth of previous." (mapcar '(lambda (arg) (/ arg 50)) full-range)) |
lambda
式How to write an anonymous function. 関数 mapcar
Apply a function to elements of a list. 別のバグ ... もっとも潜在的 Yet another ... of a most insidious type.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
lambda
式lambda
式"へのコメント(無し)
lambda
は、関数名を持たない無名関数を表すシンボルである。 無名関数を使う場合には、その本体を含める必要がある。
つまり、
(lambda (arg) (/ arg 50)) |
は、「arg
として渡されたものを50で割った商を返す」ような関数定義である。
たとえば、まえに、関数multiply-by-seven
があった。 それは、引数を7倍した。 引数を50で割ることと、名前がないことを除けば、この関数も似ている。 multiply-by-seven
に等価な無名関数は、つぎのようになる。
(lambda (number) (* 7 number)) |
(See 節 3.1 スペシャルフォームdefun
。)
3を7倍したければ、つぎのように書ける。
(multiply-by-seven 3) \_______________/ ^ | | 関数 引数 |
この式は、21を返す。
同様に、つぎのようにも書ける。
((lambda (number) (* 7 number)) 3) \____________________________/ ^ | | 無名関数 引数 |
100を50で割りたければ、つぎのように書ける。
((lambda (arg) (/ arg 50)) 100) \______________________/ \_/ | | 無名関数 引数 |
この式は、2を返す。 関数には100が渡され、50で割られるのである。
lambda
について詳しくは、See 節 `Lambda Expressions' in
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
mapcar
mapcar
"へのコメント(無し)
mapcar
は、順番に、第2引数の各要素で第1引数を呼び出す。 第2引数は並びであること。
たとえば、
(mapcar '1+ '(2 4 6)) => (3 5 7) |
引数に1を加える関数1+
が、リストの各要素に対して実行され、 新たなリストが返される。
これと、第1引数を残りのものに適用するapply
とを対比してほしい (apply
の説明は、 See 節 15. グラフの準備)。
one-fiftieth
の定義では、第1引数はつぎの無名関数である。
(lambda (arg) (/ arg 50)) |
そして、第2引数はfull-range
であり、 list-for-graph
に束縛される。
式全体はつぎのようになる。
(mapcar '(lambda (arg) (/ arg 50)) full-range)) |
mapcar
について 詳しくは、See 節 `Mapping Functions' in
関数one-fiftieth
を使って、 list-for-graph
の各要素を1/50にした要素からなるリストを作れる。
(setq fiftieth-list-for-graph (one-fiftieth list-for-graph)) |
結果はつぎのようになる。
( 9 6 5 4 3 3 2 2 1 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 4) |
書くための準備は、これでほとんど整った (情報の欠落があることに注意してほしい。 上位の範囲の多くは0であるが、それらの範囲にある単語やシンボルの個数の defun
の数が50より小さいことを意味し、 必ずしもdefun
の数が0ではない)。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
「ほとんど整った」といったのは、関数print-graph
にはバグがあるのである。 vertical-step
のオプションはあるが、 horizontal-step
のオプションはない。 top-of-range
は、10間隔で10から300まである。 しかし、関数print-graph
は、1ずつ描く。
これは、もっとも潜在的なバグの典型的なものであり、 無視したことによるバグである。 機能としては存在しないので、コードに書かれておらず、 コードを調べてもこの種のバグは発見できない。 最良の行動は、プログラムを初期の段階で何回もテストすることであり、 コードを可能な限り理解しやすく変更しやすくしておくことである。 たとえすぐにではなくても、最終的には、書いたコードは書き直すことになることを 理解しておいてほしい。 実行するのは難しい格言である。
関数print-X-axis-numbered-line
を直す必要がある。 そして、関数print-X-axis
とprint-graph
も、 対応するように直す必要がある。 多くを直す必要はない。 ちょっとしたことが1つである。 目盛に数字を合わせるだけである。 これには少々考える必要がある。
つぎに修正したprint-X-axis-numbered-line
を示す。
(defun print-X-axis-numbered-line (number-of-X-tics X-axis-leading-spaces &optional horizontal-step) "Print line of X-axis numbers" (let ((number X-axis-label-spacing) (horizontal-step (or horizontal-step 1))) (insert X-axis-leading-spaces) ;; まえにある余分な空白を削除する (delete-char (- (1- (length (int-to-string horizontal-step))))) (insert (concat (make-string ;; 空白を挿入する (- (* symbol-width X-axis-label-spacing) (1- (length (int-to-string horizontal-step))) 2) ? ) (int-to-string (* number horizontal-step)))) ;; 残りの数を挿入する (setq number (+ number X-axis-label-spacing)) (while (> number-of-X-tics 1) (insert (X-axis-element (* number horizontal-step))) (setq number (+ number X-axis-label-spacing)) (setq number-of-X-tics (1- number-of-X-tics))))) |
Infoで読んでいる場合には、 新しい版のprint-X-axis
やprint-graph
もあるので、これらを評価する。 印刷物で読んでいる場合には、変更した行をつぎに示してある (コードを全体は多すぎる)。
(defun print-X-axis (numbers-list horizontal-step) "Print X axis labels to length of NUMBERS-LIST. Optionally, HORIZONTAL-STEP, a positive integer, specifies how much an X axis label increments for each column." ;; Value of symbol-width and full-Y-label-width ;; are passed by `print-graph'. (let* ((leading-spaces (make-string full-Y-label-width ? )) ;; symbol-width は graph-body-print が与える (tic-width (* symbol-width X-axis-label-spacing)) (X-length (length numbers-list)) (X-tic (concat (make-string ;; 空白文字の文字列を作る (- (* symbol-width X-axis-label-spacing) (length X-axis-tic-symbol)) ? ) ;; 空白を目盛のシンボルに繋げる X-axis-tic-symbol)) (tic-number (if (zerop (% X-length tic-width)) (/ X-length tic-width) (1+ (/ X-length tic-width))))) (print-X-axis-tic-line tic-number leading-spaces X-tic) (insert "\n") (print-X-axis-numbered-line tic-number leading-spaces horizontal-step))) |
(defun print-graph
(numbers-list &optional vertical-step horizontal-step)
"Print labelled bar graph of the NUMBERS-LIST.
The numbers-list consists of the Y-axis values.
Optionally, VERTICAL-STEP, a positive integer,
specifies how much a Y axis label increments for
each line. For example, a step of 5 means that
each row is five units.
Optionally, HORIZONTAL-STEP, a positive integer,
specifies how much an X axis label increments for
each column."
(let* ((symbol-width (length graph-blank))
;;
|
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
インストールしたら、コマンドprint-graph
をつぎのようにして呼ぶ。
(print-graph fiftieth-list-for-graph 50 10) |
グラフはつぎのとおりである。
1000 - * ** ** ** ** 750 - *** *** *** *** **** 500 - ***** ****** ****** ****** ******* 250 - ******** ********* * *********** * ************* * 50 - ***************** * * | | | | | | | | 10 50 100 150 200 250 300 350 |
関数のもっとも大きなグループは、10から19個の単語やシンボルを含むものである。
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |