当面の目標は、単語と記号の合計が10個未満の関数定義がいくつあり、10〜19個のものがいくつあり、20〜29個のものがいくつあるか、といった情報をまとめたリストを作成することです。
数値がソートされたリストがあれば、これは簡単です。リストの中で10より小さい要素がいくつあるか数え、次に、今数えた数値を飛ばして、20より小さい要素がいくつあるか数え、さらに今数えた数値を飛ばして、30より小さい要素がいくつあるか数える、という具合に進めます。10、20、30、40といった各数値は、その範囲の上限より1大きい値です。このような数値のリストをtop-of-rangesリストと呼ぶことができます。
望めばこのリストを自動的に生成することもできますが、手動でリストを作成する方が簡単です。こちらがそのリストです:
(defvar top-of-ranges '(10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300) "List specifying ranges for `defuns-per-range'.")
範囲を変更するには、このリストを編集します。
次に、各範囲内の定義数のリストを作成する関数を記述する必要があります。当然ながら、この関数はsorted-lengthsとtop-of-rangesのリストを引数として受け取る必要があります。
defuns-per-range 関数は、2つの処理を繰り返し行う必要があります。具体的には、現在の
top-of-range 値で指定された範囲内の定義数をカウントすることと、現在の範囲内の定義数をカウントした後に top-of-ranges
リストの次の上位値へ遷移することです。これらの処理はいずれも反復的であるため、while
ループを用いて実現できます。一方のループは現在の top-of-range 値で定義された範囲内の定義数をカウントし、もう一方のループは各
top-of-range 値を順に選択します。
各範囲に対してsorted-lengthsリストの複数のエントリがカウントされます。つまり、sorted-lengthsリストのループは、大きな歯車の中にある小さな歯車のように、top-of-rangesリストのループの内側に位置することになります。
内側のループは、範囲内の定義数をカウントします。これは、これまでに見てきたタイプの単純なカウントループです。(インクリメントカウンタを用いたループを参照してください。)ループの真偽判定では、sorted-lengthsリストの値が範囲の先頭の現在の値よりも小さいかどうかを判定します。小さい場合、関数はカウンタをインクリメントし、sorted-lengthsリストの次の値を判定します。
内側のループは次のようになります。
(while length-element-smaller-than-top-of-range (setq number-within-range (1+ number-within-range)) (setq sorted-lengths (cdr sorted-lengths)))
外側のループは、top-of-ranges
リストの最小値から開始し、その後、続くより高い値を順に設定する必要があります。これは、次のようなループで実現できます。
(while top-of-ranges body-of-loop... (setq top-of-ranges (cdr top-of-ranges)))
2つのループを合わせると、次のようになります。
(while top-of-ranges ;; Count the number of elements within the current range. (while length-element-smaller-than-top-of-range (setq number-within-range (1+ number-within-range)) (setq sorted-lengths (cdr sorted-lengths))) ;; Move to next range. (setq top-of-ranges (cdr top-of-ranges)))
さらに、外側ループの各回路において、Emacsはその範囲内の定義数(number-within-rangeの値)をリストに記録すべきです。この目的にはconsを使用できます。(consを参照してください。)
cons
関数は正常に動作しますが、生成されるリストの先頭に最大範囲の定義数が、末尾に最小範囲の定義数が含まれるという問題があります。これは、cons がリストの先頭に新しい要素を追加することに加え、2つのループが lengths& rsquo;
リストを低い方から順に処理しているため、defuns-per-range-list
の結果として最大の数値が最初に来てしまうからです。しかし、グラフは値の小さい順から大きい順へと表示したいはずです。解決策は、defuns-per-range-list の順序を反転させることです。これは、リストの順序を反転させる
nreverse 関数を使用することで実現できます。
例えば、
(nreverse '(1 2 3 4))
生成する:
(4 3 2 1)
なお、nreverse
関数は破壊的です—つまり、適用先のリストを変更します。これは、非破壊的な car や
cdr 関数とは対照的です。この場合、元の defuns-per-range-list は不要であるため、それが破壊されても問題ありません。(reverse 関数はリストの逆順コピーを返し、元のリストはそのまま保持します。)
これらをまとめると、defuns-per-rangeは次のようになります。
(defun defuns-per-range (sorted-lengths top-of-ranges)
"SORTED-LENGTHS defuns in each TOP-OF-RANGES range."
(let ((top-of-range (car top-of-ranges))
(number-within-range 0)
defuns-per-range-list)
;; Outer loop.
(while top-of-ranges
;; Inner loop. (while (and ;; Need number for numeric test. (car sorted-lengths) (< (car sorted-lengths) top-of-range))
;; Count number of definitions within current range. (setq number-within-range (1+ number-within-range)) (setq sorted-lengths (cdr sorted-lengths))) ;; Exit inner loop but remain within outer loop.
(setq defuns-per-range-list
(cons number-within-range defuns-per-range-list))
(setq number-within-range 0) ; Reset count to zero.
;; Move to next range. (setq top-of-ranges (cdr top-of-ranges)) ;; Specify next top of range value. (setq top-of-range (car top-of-ranges)))
;; Exit outer loop and count the number of defuns larger than ;; the largest top-of-range value. (setq defuns-per-range-list (cons (length sorted-lengths) defuns-per-range-list))
;; Return a list of the number of definitions within each range, ;; smallest to largest. (nreverse defuns-per-range-list)))
この関数は、1点だけ微妙な仕様がある以外はシンプルです。内側ループの真偽判定は次のようになっています。
(and (car sorted-lengths)
(< (car sorted-lengths) top-of-range))
こんな風にではなく:
(< (car sorted-lengths) top-of-range)
このテストの目的は、sorted-lengths
リストの最初の項目が範囲の上限値よりも小さいかどうかを判定することです。
テストの簡易版は、sorted-lengths リストに nil の値が含まれていない限り正常に動作します。その場合、(car
sorted-lengths) 式関数は nil を返します。< 関数は、数値を空リストである nil
と比較できないため、Emacs はエラーを通知し、関数が実行を継続しようとするのを阻止します。
カウンターがリストの末尾に達すると、sorted-lengths リストは常に nil になります。つまり、テストの簡易版で defuns-per-range 関数を使用しようとしても、失敗することになります。
(car sorted-lengths)式をand式と組み合わせて使用することで、この問題を解決します。(car
sorted-lengths)式は、リスト内に少なくとも1つの数値が含まれている限りnil以外の値を返し、リストが空の場合はnilを返します。and式はまず(car sorted-lengths)式を評価し、それがnilであれば、<式を評価せずにfalseを返します。ただし、(car
sorted-lengths)式がnil以外の値を返す場合、and式は<式を評価し、その値をand式の値として返します。
こうすれば、エラーを回避できます。
以下は defuns-per-range 関数の簡単なテストです。まず、(短縮された)top-of-ranges リストを値のリストにバインドする式を評価し、次に sorted-lengths リストをバインドする式を評価し、最後に defuns-per-range 関数を評価します。
;; (Shorter list than we will use later.)
(setq top-of-ranges
'(110 120 130 140 150
160 170 180 190 200))
(setq sorted-lengths
'(85 86 110 116 122 129 154 176 179 200 265 300 300))
(defuns-per-range sorted-lengths top-of-ranges)
返されたリストは次のようになっています。
(2 2 2 0 0 1 0 2 0 0 4)
確かに、sorted-lengths リストには 110 未満の要素が 2 つ、110 から 119
の間の要素が 2 つ、120 から 129 の間の要素が 2 つ、といった具合にあります。値が 200 以上の要素は 4 つあります。