おもこん

おもこんは「思いつくままにコンピュターの話し」の省略形です

徒然Ruby(38)RDoc

今回はRubyプログラムから自動的にドキュメントを作成するRDocについて書きたいと思います。 私はこのことについて、エキスパートではありません。 この記事も、初心者の体験談だと考えてください。

どのようなプログラムに有効か?

クラスを定義するプログラムに対して有効です。 トップレベルのメソッドだけからなるプログラムだとあまり意味がありません。

というのは、RDocはプログラムを解析して、クラスとメソッドを抜き出してドキュメントを作るものだからです。 クラスのないプログラム、例えばトップレベルのメソッドだけで作ったプログラムでは、抜き出すものがありません。 そのようなプログラムにRDocを適用しても、できあがったドキュメントはとても悲しいものになってしまいます。

RDocの使い方。

Rubyプログラムのあるディレクトリで「rdoc」とコマンドラインに打ち込めば最低限の動作はします。 docディレクトリにHTMLファイルができあがるので、ダブルクリックして見てみましょう。 クラスとメソッドが表示されますね。

名前だけでは寂しいですから、ちょっと説明を書き加えましょう。 そのためには、そのクラスまたはメソッドの直前にコメントを書きます。 例えば次のようなコメントをクラス定義の直前に書き加えます。

=begin rdoc
方程式 ax+by=1 を表すクラス
インスタンスを生成するときにaとbを引数で与える。
solveメソッドで解を得ることができる。
=end

このように、=begin=endで挟まれた部分はコメントになります。 #を使ったコメントでもOKです。 このようなコメントはできあがったHTMLのクラスのところに表示されるようになります。

メソッドについても同様です。

マークアップ

RDoc独自のマークアップがあります。

  • 段落の間は空行で区切る
  • -または+で始めるとリストを作ることができる
  • []で囲んだ文字を項目とし、項目リストを作ることができる。::で項目と説明を区切る方法も可能
  • 1.で順序付きリスト、a.で文字による順序付きリストになる
  • ===・・・で見出しになる

その他についてはRubyのドキュメントを参考にしてください。

オプション

オプションはたくさんありますが、良く使われるのはタイトルとメイン画面(ホーム画面)でしょう。

  • --titleオプションでタイトルを設定。これがブラウザのタブになる(HTMLのtitleタグ相当)
  • --mainで指定したファイルがホーム画面になる。README.mdなどを指定することが多い

Rakeとの連動

RakefileにRDocでドキュメントを生成するタスクを定義できます。 例えば、

require 'rdoc/task'

RDoc::Task.new do |rdoc|
  rdoc.main = "README.md"
  rdoc.title = "Math Programs"
  rdoc.rdoc_dir = "doc"
  rdoc.rdoc_files.include("README.md", "*.rb")
end

これで、

  • メイン画面がREADME.md
  • タイトルが「Math Programs」
  • HTML生成先のディレクトリが「doc」
  • 対象ファイルがREADME.mdとRubyファイル(拡張子がrbのファイル)

となります。 rakeの引数にrdocなどをつけるとタスクが起動されます。

  • rdoc: RDocでドキュメントを生成
  • clobber_rdoc: ドキュメントをクリア(削除)=>初期状態に戻る
  • rerdoc: ドキュメントを一から生成。HTMLドキュメントのディレクトリ内は再生成したものだけになる

GitHubに「Math-programs」というレポジトリを作りました。 そこのdocディレクトリ以下にRDocで生成したドキュメントがあるので、参考にしてください。 このタイプのドキュメントはRubyを使っている人は良く見ているはずです。 例えばRakeのドキュメントがそうです。

みなさんもライブラリを作ったら、RDocでドキュメントを作ってみましょう。

リンド・パピルスの分数

QuizKnockというユーチューブ・チャンネルをごぞんじですか? クイズ番組をユーチューブにしたチャンネルです。 頻繁に更新されているので、日常的に視聴している人も多いのではないでしょか。 近頃、リンド・パピルスの数学がクイズになっていました。

最後の問題で、分子が1の分数の話がでてきます。 これを単位分数というそうです。

 \displaystyle\frac{5}{\;6\;}=\frac{1}{\;2\;}+\frac{1}{\;3\;}

左辺の分数は右辺では単位分数の和になっています。 古代エジプトでは、このように分数は単位分数の和で表していたそうです。 ただし、その分母はすべて異なるものです。 すべての分数は単位分数の和にできます。 このことは、ウィキペディアの「エジプト式分数」のページに詳しく書かれています。

自分もエジプト式分数を見つける方法を考えてみました。

簡単な例

3/4を単位分数の和にしてみましょう。 これは簡単で

 \displaystyle\frac{3}{\;4\;}=\frac{1}{\;2\;}+\frac{1}{\;4\;}

さきほどの5/6でもそうですが、右辺の最初の分数の分母は元の分数の分母より小さくなっています。 これが手がかりになります。

分数が単位分数の和に分解できること

もしも任意の分数が、

 \displaystyle\frac{a}{\;b\;}=(分母がbより小さい分数)+(単位分数)

となればこの分数は単位分数に分解できます。

  • もしも第1項が単位分数ならば、すでに単位分数の和になっている
  • そうでなければ、第1項は「(より分母が小さい分数)+(単位分数)」にすることができる。

これを繰り返すとどんどん第1項の分母が小さくなりますが、最も小さくなったとすると分母は2になります。 分母が2の分数は1/2しかありませんから単位分数です。

いずれの場合も右辺は単位分数の和になることがわかります。

不定方程式

 \displaystyle\frac{a}{\;b\;}=\frac{c}{\;d\;}+\frac{1}{bd}

となったとしましょう。 ここで、文字はすべて2以上の整数で、d<bとします。 すなわち、分数a/bは「より小さい分母の分数と単位分数の和」になったとします。 分母を払うと

 ad-bc=1

となります。 これは不定方程式です。aとbが互いに素ならば解c、dが存在し、dをb以下にとることができます。 このようにして、この問題は不定方程式に帰着させることができます。

具体例をあげましょう。 3/7を単位分数の和にしてみます。

 3d-7c=1
 3d\equiv 1\equiv 15 \quad\pmod{7}
 d=5,\;c=2
 \displaystyle\frac{3}{\;7\;}=\frac{2}{\;5\;}+\frac{1}{35}

ここまでで、より小さな分母の分数と単位分数の和になりました。 第1項に同じことをします。

 2d-5c=1
 2d\equiv 1\equiv 6\quad\pmod{5}
 d=3,\;c=1
 \displaystyle\frac{2}{\;5\;}=\frac{1}{\;3\;}+\frac{1}{15}

これより

 \displaystyle\frac{3}{\;7\;}=\frac{1}{\;3\;}+\frac{1}{15}+\frac{1}{35}

となります。

分解のしかたは一意でない

この方法で分解すると、答えはひとつしかでてきませんが、分解は一意ではありません。 たとえば、

 \displaystyle\frac{2}{21}=\frac{1}{12}+\frac{1}{84}=\frac{1}{14}+\frac{1}{42}

このように、2種類以上の単位分数の和が存在します。

上記のアルゴリズムが良いとはいえない。

上記の不定方程式を使ったアルゴリズムで求めた単位分数の和が良いとはいえません。 例えば、5/6は

 \displaystyle\frac{5}{\;6\;}=\frac{1}{\;2\;}+\frac{1}{\;3\;}

という簡単な分解がありますが、不定方程式のアルゴリズムを使うと

 \displaystyle\frac{5}{\;6\;}=\frac{1}{\;2\;}+\frac{1}{\;6\;}+\frac{1}{12}+\frac{1}{20}+\frac{1}{30}

となります。 このように和が長くなるのは良いとはいえないでしょう。 ちなみに、右辺は高校の数列で習う部分分数分解のパターンになっています。

 \begin{align*}
  & \frac{1}{\;2\;}+\frac{1}{\;6\;}+\frac{1}{12}+\frac{1}{20}+\frac{1}{30}\\
  &= \frac{2-1}{\;1\times 2\;}+\frac{3-2}{\;2\times 3\;}+\frac{4-3}{3\times 4}+\frac{5-4}{4\times 5}+\frac{6-5}{5\times 6}\\
  &= \left(\frac{1}{\;1\;}-\frac{1}{\;2\;}\right)+\left(\frac{1}{\;2\;}-\frac{1}{\;3\;}\right)+\left(\frac{1}{\;3\;}-\frac{1}{\;4\;}\right)+\left(\frac{1}{\;4\;}-\frac{1}{\;5\;}\right)+\left(\frac{1}{\;5\;}-\frac{1}{\;6\;}\right)\\
  &= 1-\frac{1}{\;6\;}\\
  &= \frac{5}{\;6\;}
  \end{align*}

ウィキペディアの記事には、単位分数に分解する別の方法も紹介されているので参考にしてください。

プログラム

アルゴリズムが確定しているので、これをプログラム化できます。 Rubyで作ったものをGitHubにあげました。

GTK -- 少し待ってから次の作業をする方法

少し待たなければいけないケース

GTK 4 は、マルチスレッドであるので、他のオブジェクトの作業完了まで待たなければいけないことがあります。

そういうときは、作業完了を知らせるシグナルを捕まえれば良いのです。 たいてい、これで解決できます。 具体的にはシグナルに、「他のオブジェクトの作業完了後にこちらで行いたいプログラム」をハンドラーとして結合します。

ところが、そのようなシグナルがない場合もあります。 自分が困ったのは、GtkColumnViewで、リストを表示するプログラム(Tcsv)でした。 私の想像では、次のようなことが行われると考えています(確証なし)。

  1. リストにアイテムを追加する
  2. リスト(GListModel)が、"items-changed"シグナルを発行する
  3. そのシグナルを受けてGtkColumnView(または関連オブジェクト)が、「GtkScrollWindowの縦方向のスクロールに関連するGtkAdjustment」のupperプロパティを1行分増やす。
  4. GtkAdjustmentは、その変更完了時に"changed"シグナルを発行する。
  5. そのシグナルにもとづき、GtkScrolledWindowの縦方向のスクロールバー(GtkScrollBar)を調整する

行の追加と同時に、新たな行(最終行)を表示するために末尾までスクロール(GtkAdjustmentのvalueプロパティをupperから1ページ分(page-sizeプロパティ)前にセットする)したかったので、次のようなことをしました。

行追加直後にGtkAdjustmentのvalueプロパティを更新する

この方法は、GtkAdjustmentのupperプロパティが更新されていないのでうまくいきません。 つまり、行の追加がGtkAdjustmentやGtkScrollWindowに反映されていないのです。 そこで、次のように方法を変えました。

GtkAdjustmentの"changed"シグナルを捕らえてvalueプロパティを更新する

この方法は、GtkAdjustmentの変更とGtkScrollBarの変更はできるのですが、画面がスクロールされませんでした。 私の想像では、GtkAdjustmentのvalueプロパティ変更がGtkScrolledWindowに伝わっていないと思われます。

GtkAdjustmentは2回シグナルを発行します。

  • upperが変更されたときに"changed"シグナルを発行する
  • valueが変更されたときに"value-changed"シグナルを発行する

GtkAdjustmentのドキュメントによると、これらのシグナルが続けて発行されるとき、それらはリカーシブ(再発行)ではなくリスタート(再スタート)されるとのことです。 つまり、シグナルは2回発行されずに、1回めの途中でそれが(キャンセルされ)再度スタートから発行手続きが始まる、ということです。 このせいかどうか分かりませんが(そうだとするとおかしな点もある)GtkScrolledWindowはupperの変更に対応しているだけで、valueの変更に対してスクロールしていません。

このあたりで行き詰まりました。

少し待ってから作業する

GtkScrolledWindowがリストの1行追加野処理を完了するまで、GtkAdjustmentのvalueを更新するのを待つ必要がありそうです。 しかし、それに適するシグナルがGtkScrolledWindowにはありません。 そうなると、「一定時間待ち、その間にGtkScrollWindowに作業してもらう」ことになります。 それを探していてg_timeout_add_onceを見つけました。

guint
g_timeout_add_once (
  guint interval,
  GSourceOnceFunc function,
  gpointer data
)
  • interval: 待ち時間。単位はミリ秒。この時間だけ待った後にfunctionが呼び出される
  • function: 待ち時間後に呼び出される関数。関数の形式は後述。
  • data: 関数に渡す引数

関数の形式は次のとおりです。

void
(* GSourceOnceFunc) (
  gpointer user_data
)

関数の引数はさきほどのdata(第3引数)です。

これを使って、リストに1行追加し、0.1秒待ってvalueプロパティを更新したら、期待した結果を得ることができました。 0.1秒というのは、経験値であり、理論的にそれが妥当かどうかはわかりません。 経験的には0.1秒あれば1行追加に対してGtkColumnViewもGtkScrolledWindowも対応が完了していました。

この方法が最適解とはいえないと思います。 しかし、現実には有効な方法でしたので、ここに記録しておくことにしました。

この関数を使ったtcsvwindow.cが次のレポジトリに収められています。 昨日のアップデートです。 scroll関数が上記のfunctionに相当します。

CSVファイルを編集する「tcsv」バージョン0.7

Cをしばらく休む予定だったのが、そうもいかず、tcsvの開発をしていました。 今日、バージョン0.7をGitHubにアップロードしました。

今回のバージョンは0.7で依然として開発版ですが、実用的なものになっています。 これでtcsvの開発は一区切りついた形です。

今回新しい版を作成したのは、GTK 4チュートリアルのセクション30で得られた技術を取り入れたからです。 また、プログラムのデータ構造をGtkStringListからGListStore+TCsvStrに変更したのも大きいです。

これで、GtkColumnViewとGtkTextを使った編集、そしてカレント・レコード(カレント行)を作る技術については、一応完成したといえます。 詳しくは、上記レポジトリのドキュメントを参照してください。

数学の英語

「数学の英語」とまぎらわしいタイトルにしてしまいましたが、「数式などを英語でどのように表現するか」という意味です。 ネットを探したら、こんなブログを見つけました。

Glatsという会社の運営するKimini英会話のブログです。 別にその会社を勧めるわけではありませんので、誤解なさらぬようお願いします。

実は、私は今年後半から数学を英語で教えるボランティアをする予定なので、あらためてネットで「数学の英語」を調べていました。 そこで上記のブログを発見したわけです。

数式の読み方はあまり英語の授業で習いませんね。 このブログは英語で習わない部分を補完するのに役立ちます。

 1+2=3

日本語では「いち たす に は さん」「いち プラス に イコール さん」「いち たす に は さん に ひとしい」などと言いますが、英語ではどうでしょうか? ブログにあるように、

  • One plus two equals three.
  • One plus two is three.
  • One plus two is equal to three.
  • One and two is three.
  • The sum of one and two is three.

などが考えられます。

掛け算の方が変化があって、たとえば

 2\times 3=6

は、

  • Two times three equals six.
  • Two multiplied by three is six.
  • The product of two and three is six.

など。 「times」が掛け算を表すところがおもしろいですね。 「two times」というのは、通常「2回」という意味に使われ、「twice」と同じです。 それを数式で使うというのは、「2回分の3は6に等しい」ということなんでしょうか。 「times」を使う表現が簡単なので、一番使われるように思います。

はじめに紹介したブログには、他の表現も書かれているので、興味のある方はご覧になってください。

今後、自分の勉強を兼ねて「数学の英語」をブログに書いてみたいと思います。

GTK 4 チュートリアルに新セクション

GTK 4チュートリアルに新たにセクション30を追加しました。 前回書いたtcsvで得られたテクニックである次の2点がその内容です。

  • 編集されたセルをリストのアイテムにコピー(バインディング)する方法
  • 動的にリストのアイテムの変化をGtkListViewのセルに反映させる方法

これらはGtkSignalListItemFactoryを使うことによって実現できます。 もうひとつのファクトリーであるGtkBuilderListItemFactoryは、変化のない静的リストを表示するには便利なのですが、応用がききません。 このことはずいぶん前から分かってはいたのですが、今回のセクション執筆でより明確になりました。

詳しくは、GTK 4チュートリアルのセクション30をご覧ください。

CSVファイルを編集するプログラム「tcsv」

GitHubのtcsvレポジトリをアップデートしました。 少々大きいプログラムで全体を見直したので、時間がかかり、その間ブログの更新ができませんでした。

CSVとは

CSVとは、コンマと改行で区切られた2次元データ形式のことです。 2次元データとは、エクセルデータのように縦横に長方形状に広がったデータです。 そして、CSVはテキストファイルで、データひとつひとつがコンマと改行で区切られます。 1行に含まれるデータ数は、すべての行において同一でなければなりません。 また、最初の行は見出し行(ヘッダー)となります。

CSVは簡単なデータ構造なので、様々な2次元データ、例えばエクセル、データベースなどでインポート/エクスポートに使われます。

tcsvとは

tcsvはC言語で書かれており、GTK 4の上で動くアプリケーションです。 tcsvはCSVファイルを読み書きでき、編集をすることができます。 単にデータの追加/削除だけでなく、フィールドの変更もできます。

tcsvのスクリーンショット

tcsvの特徴

tcdvはGTK 4のGtkColumnViewの良い例となっています。 GtkColumnViewの例では単に表示のみをするのが多いのですが、tcsvではGtkTextを使って入力することをサポートしています。 ちょうどエクセルのセルに書き込んで保存できるようなイメージです。 これを実現するには、シグナル・ファクトリーを使い、GtkTextからリストのアイテムへのバインディングが必要になります。 つまり、表示とは逆方向のバインディングです。

このテクニックについてはGTK 4チュートリアルに書き加えたいと考えています。

tcsvは役立つのかというと、まあまあ、と答えるしかありません。 もちろん、実用的であるのは間違いないのですが、同じ作業はエクセルでもできます。 ですから、どちらかというと、GTK 4のGtkColumnViewを試して見るためのプログラムとして意味があったと思います。

今回のアップデートについて

今回のアップデートは大きなもので、1464行の追加と2287行の削除を行いました。

  • バグの修正。特にメモリーリーク。
  • バインディングの方法をより簡単なものに変更
  • GTK 4とGLibの新しいバージョンへの対応(GTK 4.8、GLib 2.74.3)
  • プログラムの無駄を省き、よりスリムに。使用するクラスもより簡明なものを採用。

これでしばらくはアップデートは必要ないはずです。

このところ、CとGTK 4に取り組んでいましたが、それは一段落したので、このブログの更新頻度をあげたいと思っています。