[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C. ラベル付きグラフ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Full%20Graph"
"intro/C.ラベル付きグラフ"へのコメント(無し)

座標軸が表示されていると、グラフの理解に役立つ。 これらは尺度を与えてくれる。 前節(see 節 15. グラフの準備)では、 グラフの本体を表示するコードを書いた。 ここでは、本体自身に加えて、ラベルを付けた垂直軸と水平軸も 表示するコードを書く。

Labelled Example Graph

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Labelled%20Example"
"intro/LabelledExampleGraph"へのコメント(無し)

バッファへの挿入はポイントの右下へ向かって行われるので、 グラフを描く新しい関数では、まず、Y軸、つまり、垂直軸を描いてから、 グラフの本体を描き、最後に、X軸、つまり、水平軸を描く。 この描く順序から、関数の内容が決まる。

  1. コードの初期設定。

  2. Y軸を描く。

  3. グラフの本体を描く。

  4. 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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.1 print-graphの変数リスト

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=print-graph%20Varlist"
"intro/C.1print-graphの変数リスト"へのコメント(無し)

関数print-graphを書く場合、最初の仕事は、 let式の変数リストを書くことである (関数を対話的にしたり、説明文字列の内容については、 しばらく手をつけないでおく)。

変数リストでは、数個の変数を設定する。 明らかに、垂直軸のラベルの先頭は少なくともグラフの高さである必要があるので、 この情報をここで得ておく必要がある。 関数print-graph-bodyもこの情報を必要とすることに注意してほしい。 異なる2つの場所でグラフの高さを計算する理由はないので、 ここでの計算を利用するようにprint-graph-bodyの以前の定義を変更する。

同様に、X軸を描く関数と関数print-graph-bodyの両者は、 各シンボルの幅を知る必要がある。 この計算もここで行い、print-graph-bodyの以前の定義を変更する。

水平軸のラベルの長さは少なくともグラフの幅である必要がある。 しかし、この情報は水平軸を描く関数でのみ使われるので、 ここで計算する必要はない。

以上の考察から、print-graphletの変数リストはつぎのようになる。

 
(let ((height (apply 'max numbers-list)) ; 第1版
      (symbol-width (length graph-blank)))

あとでわかるように、この式は実は正確ではない。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.2 関数print-Y-axis

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=print-Y-axis"
"intro/C.2関数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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.2.1 余りの計算

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Compute%20a%20Remainder"
"intro/C.2.1余りの計算"へのコメント(無し)

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-graphlet式に埋め込む。

 
(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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.2.2 Y軸の要素の作成

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Y%20Axis%20Element"
"intro/C.2.2Y軸の要素の作成"へのコメント(無し)

垂直軸を書くときには、`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

GNU Emacs Lispリファレンスマニュアル

関数int-to-stringは、文字列を繋げる式で使われており、 数を文字列に変換する。 この文字列には、まえに置く空白文字や目盛が繋げられる。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.2.3 Y軸のコラムの作成

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Y-axis-column"
"intro/C.2.3Y軸のコラムの作成"へのコメント(無し)

これまでの関数は、 垂直軸のラベルとして挿入する数字や空白から成る文字列のリストを 生成する関数を作るために必要なすべての道具を提供する。

 
(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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.2.4 print-Y-axisの最終版

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=print-Y-axis%20Final"
"intro/C.2.4print-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を試せる。

  1. インストールする。

     
    Y-axis-label-spacing
    Y-axis-tic
    Y-axis-element
    Y-axis-columnprint-Y-axis
    

  2. つぎの式をコピーする。

     
    (print-Y-axis 12 5)
    

  3. バッファ`*scratch*'に切り替え、 軸のラベルを書き始めたい場所にカーソルを移動する。

  4. M-ESC(eval-expression)とタイプする。

  5. C-yyank)で、graph-body-print式を ミニバッファにヤンクする。

  6. RETを押して、式を評価する。

Emacsは、先頭が`10 - 'であるようなラベルを垂直に表示する (関数print-graphheight-of-top-lineの値を渡し、 その値は15になる)。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.3 関数print-X-axis

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=print-X-axis"
"intro/C.3関数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段階の処理になる。

  1. X軸の目盛を描く関数print-X-axis-tic-lineを書く。

  2. X軸の数字を描く関数print-X-axis-numbered-lineを書く。

  3. print-X-axis-tic-lineprint-X-axis-numbered-lineを使って、 軸の2つの行を描く関数print-X-axisを書く。

C.3.1 X軸の目盛    Create tic marks for the horizontal axis.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.3.1 X軸の目盛

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=X%20Axis%20Tic%20Marks"
"intro/C.3.1X軸の目盛"へのコメント(無し)

最初の関数は、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に値が設定されたかどうかを調べる。 設定されていない場合には、boundpnilを返す。 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-graphfull-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-lineprint-X-axis-numbered-lineを 使うprint-X-axisを書く必要がある。

関数では、print-X-axis-tic-lineprint-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を試してみよう。

  1. X-axis-tic-symbolX-axis-label-spacingprint-X-axis-tic-lineとともに X-axis-elementprint-X-axis-numbered-lineprint-X-axisをインストールする。

  2. つぎの式をコピーする。

     
    (progn
     (let ((full-Y-label-width 5)
           (symbol-width 1))
       (print-X-axis
        '(1 2 3 4 5 6 7 8 9  15 16))))
    

  3. バッファ`*scratch*'に切り替え、 軸のラベルを描き始める場所にカーソルを置く。

  4. M-ESC(eval-expression)とタイプする。

  5. C-yyank)でテスト用の式をミニバッファにヤンクする。

  6. RETを押して式を評価する。

Emacsはつぎのように水平軸を表示する。

 
     |   |    |    |    |
     1   5   10   15   20



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.4 グラフ全体の表示

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Print%20Whole%20Graph"
"intro/C.4グラフ全体の表示"へのコメント(無し)

グラフ全体を表示する準備ができた。

正しいラベルを付けたグラフを描く関数は、 まえに作った概略(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-graphlet式で 計算される。 そこで、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))
         ;; height は、最大の数であり
         ;; 表示幅がもっとも大きくなる
         (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)))
         (vertical-step (or vertical-step 1))
         (full-Y-label-width
          (length
           (concat
            (int-to-string
             (* height-of-top-line vertical-step))
            Y-axis-tic))))

    (print-Y-axis
     height-of-top-line full-Y-label-width vertical-step)
    (graph-body-print
     numbers-list height-of-top-line symbol-width)
    (print-X-axis numbers-list)))

C.4.1 print-graphのテスト    Run a short test.
C.4.2 単語やシンボルの個数のグラフ    Executing the final code.
C.4.3 グラフ    The graph itself!



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.4.1 print-graphのテスト

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Test%20print-graph"
"intro/C.4.1print-graphのテスト"へのコメント(無し)

関数print-graphを数の短いリストで試してみよう。

  1. Y-axis-columngraph-body-printprint-graph (および、残りのコード)をインストールする。

  2. つぎの式をコピーする。

     
    (print-graph '(3 2 5 6 7 5 3 4 6 4 3 2 1))
    

  3. バッファ`*scratch*'に切り替え、 軸のラベルを描き始めたい場所にカーソルを置く。

  4. M-ESC(eval-expression)とタイプする。

  5. C-yyank)で、ミニバッファにテスト用の式をヤンクする。

  6. RETを押し、式を評価する。

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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.4.2 単語やシンボルの個数のグラフ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Graphing%20words%20in%20defuns"
"intro/C.4.2単語やシンボルの個数のグラフ"へのコメント(無し)

グラフ作成に必要なコードはすべて書いた。 単語やシンボルの個数が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つの関数mapcarlambdaを使うと、 つぎの小さな関数でできる。

 
(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

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=lambda"
"intro/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

GNU Emacs Lispリファレンスマニュアル
。 Lispとlambda式は、λ計算(Lambda Calculus)から導かれたのである。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

関数mapcar

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=mapcar"
"intro/関数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

GNU Emacs Lispリファレンスマニュアル

関数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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

別のバグ ... もっとも潜在的

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Another%20Bug"
"intro/別のバグ...もっとも潜在的"へのコメント(無し)

「ほとんど整った」といったのは、関数print-graphにはバグがあるのである。 vertical-stepのオプションはあるが、 horizontal-stepのオプションはない。 top-of-rangeは、10間隔で10から300まである。 しかし、関数print-graphは、1ずつ描く。

これは、もっとも潜在的なバグの典型的なものであり、 無視したことによるバグである。 機能としては存在しないので、コードに書かれておらず、 コードを調べてもこの種のバグは発見できない。 最良の行動は、プログラムを初期の段階で何回もテストすることであり、 コードを可能な限り理解しやすく変更しやすくしておくことである。 たとえすぐにではなくても、最終的には、書いたコードは書き直すことになることを 理解しておいてほしい。 実行するのは難しい格言である。

関数print-X-axis-numbered-lineを直す必要がある。 そして、関数print-X-axisprint-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-axisprint-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))
         ;; height は最大の数であり
         ;; 表示幅がもっとも大きい
         (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)))
         (vertical-step (or vertical-step 1))
         (full-Y-label-width
          (length
           (concat
            (int-to-string
             (* height-of-top-line vertical-step))
            Y-axis-tic))))
    (print-Y-axis
     height-of-top-line full-Y-label-width vertical-step)
    (graph-body-print
        numbers-list height-of-top-line symbol-width)
    (print-X-axis numbers-list horizontal-step)))



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

C.4.3 グラフ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Final%20Printed%20Graph"
"intro/C.4.3グラフ"へのコメント(無し)

インストールしたら、コマンド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個の単語やシンボルを含むものである。


[ << ] [ >> ]           [表紙] [目次] [索引] [検索] [上端 / 下端] [?]