twitter のサーバから、指定したユーザの過去ログを取得し、XML っぽいものに変換する Ruby スクリプト、をRubyGems-twitter-3.6.0 に対応させた。

概要 (及び、このスクリプトについての説明)

 去年(2011年)の12月に、RubyGems-Twitter1.7.2 を用いて、最大3200件の tweets を xml 形式に変換して出力するスクリプトを作成した*1。しかしこのスクリプトは、RubyGems-Twitter3.6.0 では動作しなくなっていた。

 そこで前回のスクリプトを一行だけ書き換えて、RubyGems-Twitter 3.6.0 で動作するようにした。

動作環境

 RubyGems-Twitter3.6.0 がインストールされている環境で動作する。もしかしたら、その前後のバージョンでも動作するかもしれないが、とくに検証はしていない。

前回からの変更箇所

 その修正箇所は、一箇所だけである。具体的には、以下の行を、

nestHashData_to_nestElementsOfXML( tweet, statusElement )

以下のように置き換えた。

nestHashData_to_nestElementsOfXML( tweet.attrs, statusElement )

 つまり "tweet" のところを "tweet.attrs" に書き換えただけである。とくに動作は検証していないので、もしかしたら、なんらかのバグがあるかもしれない。とりあえず、この変更後のスクリプトは wholeLogSaver version 3.6.0 と名づけることにした。以下は、その wholeLogSaver version 3.6.0 スクリプトの全文。

wholeLogSaver Version 3.6.0

#!/usr/local/bin/ruby

require "rubygems"
require "twitter"
require "rexml/document"
require "optparse"

# wholeLogSaver ver 3.6.0
# 更新年月日: 2012. Oct. 25.
# 
# 指定したユーザの最大3200件の statuses を取得し、
# xml に整形して標準出力に出力するスクリプト。
# 実行には、RubyGem twitter が必要。
#
# 取得した statuses の key と value は、xml の要素に変換している。
# そして、1件の status を一行にまとめて出力している。
# つまり1000件の発言があるユーザを指定すれば、
# 全1000行からなるテキスト・データが出力されることになる。
#
# このスクリプトで使用している Twitter の API には制約がある。
# その制約のために、このスクリプトでは、
# 20 件 * 160 = 3200 件分の statuses しか取得できない。
# この制約は、将来、また変更されるかもしれない。
# (とくに根拠はないが、もし変更されるとすれば、おそらく悪いほうに。)
#
#
# history: 開発履歴
#
# VersionUp from ver 3 to ver 3.6.0.
# Date: 2012. Oct. 25.
#
# 前回のバージョン (wholeLogSaver ver 3) は、RubyGem twitter-1.7.2 で動作していた。
# 今回のバージョンアップは、その ver 3 を、RubyGem twitter-3.6.0 で動作するように手を加えただけのものである。
#
# その修正箇所は、一箇所のみ。具体的には、
# wholeLogSaver ver 3 における以下の行を、
# nestHashData_to_nestElementsOfXML( tweet, statusElement )
#
# wholeLogSaver ver 3.6.0 では、以下のように置き換えた。
# nestHashData_to_nestElementsOfXML( tweet.attrs, statusElement )
#
# ver 3 から ver 3.6.0 への今回のバージョンアップは、とくに詳細な動作検証をしていていないし、
# RubyGem twitter-3.6.0 の中身自体も、くわしく確認していない。
# なので、このスクリプトには、なんらかのバグが潜んでいるかもしれない。


# 入れ子状のハッシュデータを、再帰呼び出しによって xml の要素に変換するための関数。
def nestHashData_to_nestElementsOfXML(hashElement, xmlElement)
        hashElement.each { |k, v|

                if ( v.kind_of?(Hash) )
                        nestHashData_to_nestElementsOfXML( v, xmlElement.add_element(k.to_s) )
                else
                        xmlElement.add_element(k.to_s).add_text v.to_s
                end
        }
end


USAGE_TEXT = "usage: wholeLogSaver [-r] [-x] [-l] [-s] [-h] [-v] username [starting page number : Integer]"
Version = "version: 3.6.0"

WATING_TIME = 30

include_rts = 0
statusNumber = 0
page = 1

isVerbose = false
isPrint_XML_decl = false
isLatest20thStatuses = false
isSlowing = false


# OptionParser を用いて、コマンドラインからのオプションをチェック。
opt = OptionParser.new

# このスクリプトの起動時に -v が与えられているのなら、
# 現在どのような処理を行っているかを饒舌に標準出力に出力。(デバッグ用)
opt.on("-v", "Cause this script to be verbose. (for debugging)") {
        isVerbose = true
}

# このスクリプトの起動時に -x もしくは --xml が与えられているのなら、
# <?xml version='1.0' encoding='UTF-8'?> や、root エレメントなども出力する。
opt.on("-x", "--xml", "Generate refined XML document. That includes decl and root element. Default is didn't. (Default is only outputting <status></status> element.)") {
        isPrint_XML_decl = true
}

# このスクリプトの起動時に -r もしくは --rt が与えられているのなら、
# このユーザが公式 RT した tweet も取得する。
opt.on("-r", "--rt", "Get ReTweet. (That is, the output can include other users tweet.) Default is didn't.") {
        include_rts = 1
}

# このスクリプトの起動時に -l もしくは --last が与えられているのなら、
# 最新の20件のみを取得する。
# ただし同時に、何ページ目から取得するかも指定していた場合は、
# その starting page number で指定された20件を取得する。
opt.on("-l", "--last", "Retrieve only latest 20 tweets. But if you specify starting-page-number by command line, then retrieve the page number's 20 tweets.") {
        isLatest20thStatuses = true
}

# このスクリプトの起動時に -s もしくは --slow が与えられているのなら、
# ループ内で、WATING_TIME 秒 (30秒ほど) 休息しつつ、処理を行う。
# Twitter の API は、1時間に150回までのアクセスしか受け付けていないため。
opt.on("-s", "--slow", "Cause this script to be slow, actually to wait #{WATING_TIME} seconds in every time in each loop. This option is for avoiding the restricion of Twitter's API. Twitter's unauthenticated API calls are permitted 150 requests per hour.") {
	isSlowing = true
}

#"Cause this script to be slow, actually to wait 60 seconds in every time in each loop. There are some restrictions on Twitter's API. Unauthenticated API calls are permitted 150 requests per hour. This option is for avoiding this restriction."

opt.parse!(ARGV)

if ARGV[0] == nil then
        puts "Username is not specified. Pleas, enter username. (Username is Twitter's screen name.)"
        puts USAGE_TEXT
        ExitStatus = 0
        exit(ExitStatus)
else
        username = ARGV[0]
end

if ARGV[1] != nil then
        begin
                page = Integer(ARGV[1])
        rescue
                puts "Maybe, this second argument is not integer number. Please, enter page's number as a positive integer."
                puts USAGE_TEXT
                ExitStatus = 0
                exit(ExitStatus)
        end
end

if isVerbose then
        puts "include_rts is #{include_rts}"
        puts "stating page is #{page}"
        puts "username is #{username}"
end


doc = REXML::Document.new
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
statuses = doc.add_element("statuses")

if isPrint_XML_decl then
        puts doc.xml_decl
        puts "<statuses>"
end

tweets = Twitter.user_timeline(username, { "include_rts"=>include_rts, "page"=>page} )

until tweets.empty?

        tweets.each { |tweet|
                statusElement = statuses.add_element("status")
                nestHashData_to_nestElementsOfXML( tweet.attrs, statusElement )
                puts statusElement
                statusNumber = statusNumber + 1
        }

        page = page + 1

        if isLatest20thStatuses then
                break
        end

	if isSlowing then
		sleep(WATING_TIME)
	end

        tweets = Twitter.user_timeline(username, { "include_rts"=>include_rts, "page"=>page} )
end

if isPrint_XML_decl then
        puts "</statuses>"
end

if isVerbose then
        puts "Last page is #{page-1}"
        puts "#{statusNumber} statuses is printed. (statusNumber is #{statusNumber})"
end

*1:[http://d.hatena.ne.jp/r_coppelia/20111203/1322853481:title]

twitter のサーバから、指定したユーザの過去ログを取得し、XML っぽいものに変換する Ruby スクリプト。

まずはじめに RubyGems twitter のインストール。

 先週、FreeBSD の ports に何か面白そうなものがないかなと、

> ls -d /usr/ports/*/*[Tt]witter*

とか、

> ls -d /usr/ports/*/*[Tt]weet*

 などと入力して遊んでいたら、/usr/ports/net/rubygem-twitter/ というものを見つけた。Ruby による twitter API のラッパーのよう。
https://github.com/jnunemaker/twitter

 とりあえず、せっかくインストールしてみたので、これを用いて、指定した user の過去ログを取得して、xml に整形して、標準出力に出力するスクリプトを書いてみた。

 私は ruby には不慣れなので、とくに xml 関連の処理の部分などは、なんか変なことをやっているような気がしないでもない。

 私は、インストールは FreeBSD の ports を用いたけれど、そうでない場合は、root とかになって、

> gem install twitter

 とでも入力すれば、たぶん良いんじゃないかなと思う。

Version

 私の環境では /usr/local/lib/ruby/gems/1.8/gems/twitter-1.7.2 にインストールされているので、たぶんこれは twitter-1.7.2 というバージョンなんだと思う。

で、以下が、その RubyGems twitter を用いて作ってみたログ保存用のスクリプト。

#!/usr/local/bin/ruby

require "rubygems"
require "twitter"
require "rexml/document"
require "optparse"

# 指定したユーザの最大3200件の statuses を取得し、
# xml に整形して標準出力に出力するスクリプト。
# 実行には、RubyGem twitter が必要。
#
# 取得した statuses の key と value は、xml の要素に変換している。
# そして、1件の status を一行にまとめて出力している。
# つまり1000件の発言があるユーザを指定すれば、
# 全1000行からなるテキスト・データが出力されることになる。
# (はずであったが、改行を含む tweet を取得した場合には、
# その出力されるテキスト・データの行数も多くなる。)
#
# このスクリプトで使用している Twitter の API には制約がある。
# その制約のために、このスクリプトでは、
# 20 件 * 160 = 3200 件分の statuses しか取得できない。
# この制約は、将来、また変更されるかもしれない。
# (とくに根拠はないが、もし変更されるとすれば、おそらく悪いほうに。)


# 入れ子状のハッシュデータを、再帰呼び出しによって xml の要素に変換するための関数。
def nestHashData_to_nestElementsOfXML(hashElement, xmlElement)
        hashElement.each { |k, v|

                if ( v.kind_of?(Hash) )
                        nestHashData_to_nestElementsOfXML( v, xmlElement.add_element(k.to_s) )
                else
                        xmlElement.add_element(k.to_s).add_text v.to_s
                end
        }
end


USAGE_TEXT = "usage: wholeLogSaver.rb [-r] [-x] [-l] [-s] [-h] [-v] username [starting page number : Integer]"
Version = "version: 3"

WATING_TIME = 30

include_rts = 0
statusNumber = 0
page = 1

isVerbose = false
isPrint_XML_decl = false
isLatest20thStatuses = false
isSlowing = false


# OptionParser を用いて、コマンドラインからのオプションをチェック。
opt = OptionParser.new

# このスクリプトの起動時に -v が与えられているのなら、
# 現在どのような処理を行っているかを饒舌に標準出力に出力。(デバッグ用)
opt.on("-v", "Cause this script to be verbose. (for debugging)") {
        isVerbose = true
}

# このスクリプトの起動時に -x もしくは --xml が与えられているのなら、
# <?xml version='1.0' encoding='UTF-8'?> や、root エレメントなども出力する。
opt.on("-x", "--xml", "Generate refined XML document. That includes decl and root element. Default is didn't. (Default is only outputting <status></status> element.)") {
        isPrint_XML_decl = true
}

# このスクリプトの起動時に -r もしくは --rt が与えられているのなら、
# このユーザが公式 RT した tweet も取得する。
opt.on("-r", "--rt", "Get ReTweet. (That is, the output can include other users tweet.) Default is didn't.") {
        include_rts = 1
}

# このスクリプトの起動時に -l もしくは --last が与えられているのなら、
# 最新の20件のみを取得する。
# ただし同時に、何ページ目から取得するかも指定していた場合は、
# その starting page number で指定された20件を取得する。
opt.on("-l", "--last", "Retrieve only latest 20 tweets. But if you specify starting-page-number by command line, then retrieve the page number's 20 tweets.") {
        isLatest20thStatuses = true
}

# このスクリプトの起動時に -s もしくは --slow が与えられているのなら、
# ループ内で、WATING_TIME 秒 (30秒ほど) 休息しつつ、処理を行う。
# Twitter の API は、1時間に150回までのアクセスしか受け付けていないため。
opt.on("-s", "--slow", "Cause this script to be slow, actually to wait #{WATING_TIME} seconds in every time in each loop. This option is for avoiding the restricion of Twitter's API. Twitter's unauthenticated API calls are permitted 150 requests per hour.") {
        isSlowing = true
}


opt.parse!(ARGV)

if ARGV[0] == nil then
        puts "Username is not specified. Please, enter username. (Username is Twitter's screen name.)"
        puts USAGE_TEXT
        ExitStatus = 0
        exit(ExitStatus)
else
        username = ARGV[0]
end

if ARGV[1] != nil then
        begin
                page = Integer(ARGV[1])
        rescue
                puts "Maybe, this second argument is not integer number. Please, enter page's number as a positive integer."
                puts USAGE_TEXT
                ExitStatus = 0
                exit(ExitStatus)
        end
end

if isVerbose then
        puts "include_rts is #{include_rts}"
        puts "stating page is #{page}"
        puts "username is #{username}"
end


doc = REXML::Document.new
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
statuses = doc.add_element("statuses")

if isPrint_XML_decl then
        puts doc.xml_decl
        puts "<statuses>"
end

tweets = Twitter.user_timeline(username, { "include_rts"=>include_rts, "page"=>page} )

until tweets.empty?

        tweets.each { |tweet|
                statusElement = statuses.add_element("status")
                nestHashData_to_nestElementsOfXML( tweet, statusElement )
                puts statusElement
                statusNumber = statusNumber + 1
        }

        page = page + 1

        if isLatest20thStatuses then
                break
        end

        if isSlowing then
                sleep(WATING_TIME)
        end

        tweets = Twitter.user_timeline(username, { "include_rts"=>include_rts, "page"=>page} )
end

if isPrint_XML_decl then
        puts "</statuses>"
end

if isVerbose then
        puts "Last page is #{page-1}"
        puts "#{statusNumber} statuses is printed. (statusNumber is #{statusNumber})"
end

で、このスクリプトの使用方法。

 RubyGem Twitter をインストールしたら、上のスクリプトをコピー・ペーストして*1、たとえば wholeLogSaver.rb とか、好きな名前をつけて保存し、chmod とかで実行権限を付与。

 あとは、ログを取得したいユーザの名前を第1引数に与えて、以下のように実行してやれば、そのユーザのログを延々と twitter のサーバから取得しては、それを延々と標準出力に吐き出し続ける。

> ./wholeLogSaver.rb username

 ただし、twitter のサーバ側には制限があって、3200件より以前のログは、返してくれない。なので、3201件以上 tweet しているユーザのログは、全部取得することはできない。かなしいですね。

 いずれにしても、出力はかなり膨大になるので、リダイレクトとかして、ファイルに保存するなりしたほうが良いと思う。というか xml にしないほうが良かったような気もしないでもない。

 そんなに膨大なログとか欲しくない場合は、以下のように -l というオプションも一緒に指定しておけば、とりあえず最新の20件のみを取得し、その20件だけを出力する。なので、はじめにいろいろ試してみる場合や、使い方を忘れた場合は、とりあえずこのオプションをつけて使ったほうが良いかもしれない。

> ./wholeLogSaver.rb -l username

 また、第2引数に、数字を入力すれば、最新の投稿から数えて、その数字をだいたい20倍した件目からの tweet を延々と取得する。たとえば以下の例では、5 を指定しているので、20 * 5 -19 = 81 となるので、最新から数えて81件目からの tweets を延々と取得して、出力し続ける。

> ./wholeLogSaver.rb username 5

 この場合も、先ほどの -l オプションを併用することができる。併用すれば、81件目〜100件目までの20件のみを表示する。

 あと、そのユーザが公式 RT した他人の tweets は、デフォルトでは取得しないようにしている。それもあわせて取得したい場合は、下記のように -r オプションを指定して実行。

> ./wholeLogSaver.rb -r username

 それとあと、このスクリプトは、完全な XML ファイルを出力してはいない。もともとこのスクリプトの目的が、取得したログの件数と、保存したログファイルの行数を、きっかり同じにそろえたかったので、私にとって余分な xml の宣言とか、ルートの要素とかは、デフォルトでは出力していない*2。完全な xml ファイルを出力したい場合は、-x オプションを指定すれば良いはず。

> ./wholeLogSaver.rb -x username

 他に、-v というオプションを渡せば、スクリプト内部の引数に、どんな値が入力されたかとか、何件分の tweet を xml に整形して出力したかとかも、標準出力にいっしょに出力するけれど、これは私がデバッグ中にとりあえず使っていたオプションなので、削除しても良かったかもしれない。当然のように -x オプションといっしょに指定すると、その出力される xml ファイルは、わけのわからないものになっちゃうので、併用しないほうが絶対に良いね。

> ./wholeLogSaver.rb -v -x username

諸注意。

 あまり短時間に大量に API を呼び出し続けていると、twitter のサーバ側から、アクセスを半永久的に拒否される可能性もあると思う。なので上記のスクリプトも、実際に用いる場合は、繰り返し処理の部分に、待ち時間をはさみこんだほうが良いと思う。一応、上記のスクリプトで使用している API は、ユーザ認証不要のものではあるけれど。

追記

 実際にこのスクリプトを実行してみてわかったけれど、Twitter の 認証不要の API は、ひとつのグローバル IP アドレスに対して、1時間あたり150件までしかリクエストを受け付けてくれないそう*3

 なので、3000件以上のログのあるユーザのアカウントに対して実行すると、途中でエラーが返ってきてスクリプトが中途半端なところで終了してしまうので、二回に分けて実行するか、あるいは API の呼び出しが百十数回を越える場合は、途中で sleep などの処理を挟んで、だんだんと遅くしていくか、あるいは、エラーが返ってきた場合には、一度、休息して、その後、自動的に処理を再開するなどするように書き直したほうが良さそう。

 とりあえず上記のスクリプトには、-s というオプションを追加しておいた。このオプションを指定すれば、ループのたびごとに、30秒間の休息を取る。なので全3200件のログを取得するには、約80分ほどの時間がかかることになる。

-s オプションの使用例
> ./wholeLogSaver.rb -s username

*1:このスクリプトの一行目の #!/usr/local/bin/ruby の部分は、書き換える必要があるかもしれない。私のマシンでは、ruby は /usr/local/bin/ruby にインストールされているので、そのように書いたけれど、OS などによってインストール場所は異なるので、 which ruby とかシェルから入力してみて、たしかめておくと良いと思う。

*2:なお、実際には取得した tweet に改行文字が含まれる場合もあるので、その保存されるログファイルの行数と、ログの件数は一致しない場合がある。もうがっかり。

*3: https://dev.twitter.com/docs/rate-limiting より引用。 "Unauthenticated calls are permitted 150 requests per hour. Unauthenticated calls are measured against the public facing IP of the server or device making the request." (訳: 非認証の API 呼び出しは、1時間あたり150回まで許されている。その非認証呼び出しの回数は、グローバルな IP アドレスか、もしくはその API を呼び出したデバイスごとに、計測される。)

FreeBSD 8.1 上で netbeans 7.0 を用いて、Gtk2 アプリケーションを作成する方法。

make ではなく、gmake を使う必要がある。

 まず、FreeBSD 上の NetBeansC/C++ アプリを構築するためには、gmake を使用する必要があるようだ。その設定方法については、前回の日記に書き記しておいた。

 なので、今回はその前回に作った、標準出力に "Hello world." と表示するプログラムを作成するプロジェクトに手を加えて、"Hello World." という題名の Gtk2 の window を X Window 上に表示するプログラムを作ってみることにした。

 まず前回の main.c を以下のように書き換える。

/* 
 * File:   main.c
 * Author: あなたの名前
 *
 * Created on 2011/08/18, 15:42
 */

#include <stdio.h>
#include <stdlib.h>

#include <gtk/gtk.h>

/*
 * 
 */
int main(int argc, char** argv) {
    
    GtkWidget *window;
    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 200, 100);
    gtk_window_set_title(GTK_WINDOW(window), "Hello world.");
    gtk_widget_show(window);

    g_signal_connect(window, "destroy",
		    G_CALLBACK (gtk_main_quit), NULL);

    gtk_main();
    
    printf("Hello World is done. \n");
    return (EXIT_SUCCESS);
}

 上記のソースコードは、たしか Getting Started あたりの gtk2 についてのチュートリアルのページから拾ってきたプログラムだったと思う。(手動でコピーをかさねるうちに多少、変なぐあいに手が加わって、少々ちがったものになっているかもしもれないが)

 このソースコードNetBeans を使うまでもなく、端末から、

>gcc -Wall -g main.c -o helloworld `pkg-config --cflags gtk+-2.0` `pkg-config --libs gtk+-2.0`

 とでも入力して、リターンキーを押せば、多分、helloworld という名前の実行ファイルが生成されるだろうし、さらに端末から、

>./helloworld

 というようにその実行ファイル名を入力して、リターンキーを押せば、画面に「Hello World.」という題名のついた Gtk2 ウインドウも表示されることだろう。でもまあせっかくだし、今回は、この一連の処理を、端末からではなく、NetBeans を使って行えるようにしようというわけだ。

Gtk2 アプリを構築するために必要なインクルードファイルと、ライブラリファイルを NetBeans に教えてやる必要がある。

 このまま NetBeans に、実行ファイルを構築させようとしても、うまくはいかない。この上記のコードをコンパイルするためには、インクルードしなければならないファイルがたくさん必要であるし、コンパイルに成功したとしても、そこから実行ファイルを完成させるためには、さらにいくつかのライブラリファイルをリンクしてやる必要がある。

 つまり、実行ファイルをうまく構築するためには、NetBeans のやつに、いろいろ教えてあげる必要があるわけだ。このソースファイル(main.c)をコンパイルするためには、どのようなインクルードファイルが必要であるかとか、コンパイルずみのファイルをリンクさせて実行ファイルを生成するには、どのようなライブラリファイルをいっしょにリンクしなければならないのかなどを。その教える方法を以下の節では、順番に見ていこう。

余談

 ところで、さきほどちらっと言及したけれど、端末からなら、あっさり一行のコマンドで helloworld という実行ファイルを生成することができた。それは、なぜかといえば、そのコマンド中の pkg-config --cflags gtk+-2.0pkg-config --libs gtk+-2.0 などのおかげ。試しに端末から、

> pkg-config --cflags gtk+-2.0

 などと入力してやれば良く判る。そうすれば gtk2 アプリを構築するために必要なインクルードファイルの場所を示したディレクトリなどが、ずらずらと表示されるし、

> pkg-config --cflags gtk+-2.0

 などと入力すれば、必要なライブラリファイルなどが、ずらずらと表示されるというわけ。先ほどの一行のコマンド内では、この二つの pkg-config コマンドがバッククォートで囲まれている。なので、この二つの pkg-config の出力結果が、gcc にオプションとして与えられることになる。その与えられたオプションによって、gcc は 正しく helloworld という実行ファイルを生成することができた。(以上、余談おわり)

インクルードするべきディレクトリを NetBeans に教えてやる方法。

 さてここから本題に戻って、NetBeans にインクルードするべきディレクトリを教える方法。まず、NetBeans のプロジェクト・ウインドウにて、前回、作った "HelloWorld" というプロジェクトを右クリックし、表示されたポップアップメニューの一番下から「プロパティー」という項目を選択。すると「プロジェクトプロパティー - HelloWorld」というダイアログが開く。このダイアログの左にある「カテゴリ」の中の「構築」というノードから、「Cコンパイラ」という項目を選択。

 つぎに、このダイアログの右側にある「構成:」から「<すべての構成>」を選んで、その下の「一般」というノードから「インクルードディレクトリ」の「...」をクリック。

 すると「Debug, Release - インクルードディレクトリ」というダイアログが開くので、そのダイアログの「追加」というボタンをクリック。すると「ディレクトリを選択」というダイアログが開く。

 さて。ここで NetBeans は、しばらく放置しておいて、つぎに端末から

> pkg-config --cflags gtk+-2.0

と入力して、リターンキーを押すと、gtk2 が必要とするインクルードディレクトリが、ずらずらと端末上に表示される。たとえば "-I/usr/local/include/gtk-2.0 -I/usr/local/lib/gtk-2.0/include <以下略>" などのような感じ。

 この、ずらずらと表示された "-I/usr/local/include/gtk-2.0" などから "-I" の部分を取り除いた "/usr/local/include/gtk-2.0" の部分こそが、我々が求めているものだ。つまりこの "/usr/local/include/gtk-2.0" こそが、gtk2 アプリを構築するために必要なインクルードファイルの所在地なわけだ。

 なので、この "/usr/local/include/gtk-2.0" を、先ほどの放置しておいた NetBeans の「ディレクトリを選択」というダイアログの下のほうの「ファイル名」というところに入力して、「選択」をクリック。

 これで、われらの NetBeans は、/usr/local/include/gtk-2.0 ディレクトリにあるファイルこそが、HelloWorld というプロジェクトをコンパイルするさいには必要であることを理解してくれた。あとは同じようにして、"pkg-config --cflags gtk+-2.0" がずらずらと表示した、残りのすべてのディレクトリも、ぜんぶ追加していけば良い。それが終わったら、一応、安全のために、現在表示されているすべてのダイアログの「了解」ボタンをクリックしていって、プロジェクトに今回、加えられた設定を保存しておこう。

 これで必要なインクルードファイルは指定しおわったので、コンパイルは可能になった。しかしこのままでは、コンパイルに成功しても、そのつぎのリンクに失敗してしまう。リンクを成功させて実行ファイルを完成させるためには、リンク時に必要なライブラリファイルのことも NetBeans に教えてやらなければならない。

リンクするべきライブラリを NetBeans に教えてやる方法。

 さきほどと同じようにしてプロジェクトウインドウの "HelloWorld" を右クリックする。そして開いたポップアップメニューの一番下から「プロパティー」を選択。

 すると先ほどと同じように「プロジェクトプロパティー - HelloWorld」というダイアログが開くので、このダイアログの左にある「カテゴリ」の中の「構築」というノードから、今度は「リンカー」という項目を選択。そして右側から、先ほどと同じように <すべての構成> を選択して、下のほうにある「ライブラリ」の「...」をクリック。

 すると「Debug, Release - ライブラリ」というダイアログが開くので、ここの「ライブラリファイルを追加」をクリック。すると「ライブラリファイルを選択」というダイアログが開く。この開いたダイアログは、そのまま放置して、つぎに端末から、

> pkg-config --libs gtk+-2.0

 と入力してリターンキーを押す。すると gtk2 アプリを構築するために必要なライブラリがずらずらと表示される。たとえば、 "-L/usr/local/lib -lgtk-x11-2.0 -lgdk-x11-2.0 <以下略>" など。

 あとは、さきほどの「ライブラリファイルを選択」ダイアログに戻り、必要なライブラリファイルを追加していけば良い。うちの環境の場合は、さきほどの pkg-config の出力結果が、"-L/usr/local/lib" となっているので、まずこのダイアログで /usr/local/lib ディレクトリに移動する。そして、さきほどの pkg-config の出力結果には "-lgtk-x11-2.0" とあるので、この "-l" を "lib" と読み替えて、"libgtk-x11-2.0" で始まるライブラリファイルを、さきほどの /usr/local/lib ディレクトリのディレクトリから探す。

 とりあえず、今回の HelloWorld の場合は、libgtk-x11-2.0.so だけを指定してやれば動作するので、このファイル一つを指定したら、あとは現在開いているすべてのダイアログの「了解」ボタンを押して、ダイアログを閉じてしまおう。

コンパイルして、リンクして、主プロジェクトを実行。

 あとは、いつものようにメニューバーから「実行」をクリックして、そこから「主プロジェクトを実行」をクリックするなどしてみよう。これでしばらく待って、うまく実行ファイルの構築が成功すれば、"Hello World." というタイトルの Gtk2 ウィンドウが開くはず。うまくウインドウが表示されれば、めでたしめでたし。

必要なもの

  • /usr/ports/java/netbeans/
  • /usr/ports/devel/gmake/
  • /usr/ports/x11-toolkits/gtk20/
  • /usr/ports/devel/pkg-config/

 多分、今回、必要なものは上記のような感じだと思う。NetBeans は、ともかくとして、他のものは、Gnome とか xfce とかの最新のものを入れていれば、たぶん一緒にインストールされているんじゃないかなーと思うけれど自信はない。

 あと、多分、今回のようにいちいちインクルードディレクトリを一つ一つ指定しなくても、コンパイル時のオプションとして、`pkg-config --cflags gtk+-2.0` `pkg-config --libs gtk+-2.0` などを指定してやるだけでも、うまく実行ファイルを構築できるような気がする。

FreeBSD 8.1 上で netbeans 7.0 を用いて、C/C++ のアプリケーションを作成する方法。

/usr/local/bin/gmake を使うと良いようだ。

 なにか回避する方法があるのかもしれないが、デフォルトで設定されている /usr/bin/make を用いたのでは "-g:No such file or directory" というようなエラーがでて、うまく構築することができなかった。だが、 /usr/local/bin/gmake を用いることでこのエラーは回避できるようだった。

NetBeans が gmake を用いるように設定しなおす方法。

 というわけで、 NetBeans が /usr/local/bin/gmake を使用するように設定しなおそう。まず、NetBeans のメニューバーの「ツール」から「オプション」をクリック。そして表示された「オプション」というタイトルのついたダイアログから、「C/C++」というタグを選択。

 つぎに、このダイアログの「make コマンド:」という箇所の「/usr/bin/make」を書き換えて、「/usr/local/bin/gmake」にする。

 もしかしたら、事前に、gmake を FreeBSD にインストールしておく必要があるかもしれない。またあるいは NetBeans の設定をどうにか工夫すれば(たとえば make のオプションを細かく指定するなどで) /usr/bin/make を使用することもできるのかもしれない。

とりあえず HelloWorld というプロジェクトを作って見よう。

 せっかくなので Hello World. と出力するだけのプロジェクトを作って見ることにした。

 まずメニューバーの「ファイル」から「新規プロジェクト」をクリック。そして開いた「新規プロジェクト」というダイアログから、「カテゴリ」で「C/C++」を選択し、その右の「プロジェクト」では「C/C++アプリケーション」を選択して、「次へ >」をクリックしてみた。

「プロジェクト名」は「HelloWorld」。あとはプロジェクトの場所などは、自分の HDD 上のどこか好きな場所を指定。他の設定は、デフォルトのままにした。

 するとこんな中身の main.c ファイルが自動で生成されるので、あとは、その中身を好きなように書き換えていくだけ。

/* 
 * File:   main.c
 * Author: あなたの名前
 *
 * Created on 2011/08/18, 15:42 (現在の日付)
 */

#include <stdio.h>
#include <stdlib.h>

/*
 * 
 */
int main(int argc, char** argv) {

    return (EXIT_SUCCESS);
}

 たとえば、このファイルの main 関数を以下のような感じに、書き換えてみた。

int main(int argc, char** argv) {
    printf("Hello world. \n");
    return (EXIT_SUCCESS);
}

 これであとは、 F6 キーを押すなり、メニューバーから「実行(R)」をクリックして、「主プロジェクトを実行」なりをクリックすれば、運が良ければコンパイルに成功して、下の「出力」というウインドウに

Hello world. 


実行成功 (合計時間: < ここには実行までに要した時間が、ミリセカンド単位で表示される >ms)

 などと表示される。うまく表示されたら、めでたしめでたし。

unicodeのテスト。&#21555;&#37636;云:&#39469;表言曰:「北降人王潛等&#35498;,北相部伍,圖以東向,多作布&#22218;,欲以盛沙塞江,以大向荊州.夫備不豫設,難以應卒,宜為之防.」

吳錄云:騭表言曰:「北降人王潛等說,北相部伍,圖以東向,多作布囊,欲以盛沙塞江,以大向荊州.夫備不豫設,難以應卒,宜為之防.」

魔法少女まどか☆マギカにおける魔法少女と魔女の個体群動態

 この記事は、Population dynamics - Puella Magi Wiki の日本語訳です。第8話や、第10話などのネタバレを一部に含みます。基本的には、19 March 2011, at 14:13 時点での版を翻訳したものですが、一部の記述は、後の版をもとに校勘して置き換えています。(たとえば、原版の "Refined model" の節における "t=350" の数値をこの日本語訳では、後の版をもとに "t=300" になど)

 この記事の原著作者は、Homerun-chan さんや、Prima さんたちです。この記事は、Attribution-NonCommercial-ShareAlike 3.0 Unported の元で利用できます。(Content is available under Attribution-NonCommercial-ShareAlike 3.0 Unported.)

食物連鎖って知ってる? 学校で習ったよね? 弱い人間を魔女が食う。その魔女を私たちが食う。これがあたりまえのルールでしょ。そういう強さの順番なんだから。

 –––佐倉杏子 魔法少女まどか☆マギカ 第5話 より。

 佐倉杏子が言ったように、魔法少女と、魔女と、人間たちのこの三者の関係は、自然界における食物連鎖に似ている。しかしこの三者の関係のなかで、魔法少女と魔女の関係は、自然界における捕食者と被捕食者の関係とは異なる点がある。(第8話であきらかになったように)魔法少女のソウルジェムが完全ににごると、魔法少女は魔女に変化してしまう点である。このために、魔法少女と魔女、この両者の個体数の関係式は、より複雑になるし、標準的な人口モデルを用いたのでは、彼ら三者の個体数の動的モデルを構築することはできない。よって、彼ら三者の個体数の動的モデルを構築するためには、魔法少女-魔女の関係に特化した新たな個体群動態モデルを開発する必要がある。

モデルの仮定

 まず魔法少女と魔女を合計した個体数は、人間の個体数から彼女たちの個体数を減算することで求められる。つぎに計算を簡単にするために、我々は以下の二つの前提条件を導入し、その仮定のもとにモデルを構築することにした。まず一つに、魔法少女と魔女をあわせた全個体数は、人間の全個体数と比較すると、とても小さいであろうということ。そして二つに、魔女の活動による人間の個体数の減少も、少々ならばこの比率にとくに大きな変化をもたらさないであろうということ。

 魔法少女の個体数は、キュゥべえが契約した少女の人数分だけ増大し、逆に、魔法少女が死ぬか魔女になった件数分だけ減少する。魔女の個体数は、魔女の使い魔が成熟した件数と、魔法少女が魔女に変化した件数の合計分だけ増大し、逆に、魔法少女によって狩られた分だけ減少する。以上の仮定を要約すると、以下の変数で表すことができる。

M(t)
時間 t における魔法少女の個体数 (自然数)
W(t)
時間 t における魔女の個体数 (自然数)
C
時間 t の値が1増えるごとに、キュゥべえが契約を獲得する少女の件数 (自然数)
D
時間 t の値が1増えるごとに、魔法少女が死亡する確率 (百分率)
B
時間 t の値が1増えるごとに、魔法少女が魔女になる確率 (百分率)
K
時間 t の値が1増えるごとに、魔法少女1個体が魔女1個体を倒す確率 (百分率)
F
時間 t の値が1増えるごとに、(魔女1個体あたりの)使い魔が成熟して魔女に変化する件数 (正の有理数)

シンプルモデル

 まず我々は、簡略なモデルを構築し、これをもとにしてより洗練されたモデルを完成させた。(洗練されたモデルについては、次節の「より精巧なモデル」を参照) まず、シンプルなモデルにおいては、魔法少女の死亡率、魔法少女が魔女になる確率、そして魔女が殺される確率の三つの変数は、時間 t における魔女の個体数との間に関連性がないものとして構築を試みた。これによって、式を単純化することができるし、またこの微分方程式を閉形式解として求めることが可能になる。この仮定においては、時間 t における魔法少女と魔女の個体数の変化量は、以下の式によって求めることができる。

{\begin{align}\Delta M(t) &= C-D \times M(t)-B \times M(t)\\ &= C-(D+B) \times M(t) \\\end{align}}

{\begin{align}\Delta W(t) &= B \times M(t) + F \times W(t)-K \times M(t)\\ &= F \times W(t) +(B-K) \times M(t) \\\end{align}}

シミュレーション

 各変数に妥当と思われる値を代入し、シンプルモデルの式を Matlab を用いて100回繰り返した。

グラフは、各回ごとの魔法少女の個体数の変化
グラフの縦軸は、魔法少女 及び 魔女 の個体数を表し、グラフの横軸は、回数を表す。緑色の線が魔法少女の個体数の増減。赤色の線が魔女の個体数の増減。

 このシミュレーションで用いた値は、以下の通り。

  • C = 100;
  • D = 0.25;
  • B = 0.125;
  • F = 0.015;
  • K = 0.12;
  • M(1) = 0;
  • W(1) = 0;
  • C/(D+B) = 267;

 おそらく実際には、これらの値は不正確なものであると思われるが、これらの値を変更したとしても、上記のグラフはだいたい似通ったカーブを描くものと思われる。(基本的に、数値をいじった場合、そのカーブのするどさは異なってくるが、そのだいたいの形状には変化は現れない)

個体数の均衡

 もし、魔法少女と魔女の個体数が、双方ともに均衡するとすれば、両者の個体数は変化しない [ΔM(t) = 0 & ΔW(t) = 0] 。つまりそのためには、その回において、キュゥべえと契約して新たに魔法少女になる少女の人数と、その同じ回において死ぬか魔女になる魔法少女の人数が等しくなければならないし [C = (D + B) M(t)] 、また使い魔から魔女になる個体数と、魔法少女が倒す魔女及び魔女に変化する魔法少女の数が釣り合っていなければならない。[F * W(t) = (K - B) M(t)] 。

  • 魔法少女の個体数が均衡するならば、その状態においての魔法少女の個体数 M(t) は、C / (D + B) という式によって求まる。
  • 魔女の個体数が均衡するならば、その状態においての魔女の個体数 W(t) は、(B - K) M(t) という式によって求まる。またもし、このとき魔法少女の個体数 M(t) も均衡であるならば M(t) = C / (D + B) であるので、このときの魔女の個体数は、以下の式によって求まる。

{\begin{align} W(t) &= (B - K) \times M(t)\\ &= (B - K) \times \frac{C}{(D + B)}\\ &= C \left( \frac{B - K}{D + B} \right) \\\end{align}}

一般解

 魔女と魔法少女の個体数の一般解は、以下の通り。

  • { M(t) = \alpha e^{-(D+B)t} + \frac{C}{D+B} }
  • { W(t) = C \frac{B-K}{D+B}(\frac{1}{D+B+F}e^{-(D+B)t} + \beta e^{Ft} - \frac{1}{F}) }

 αとβは定数。もし、魔女と魔法少女が、キュゥべえによってもたらされたものだと仮定するならば、魔女及び魔法少女の t = 0 における個体数は、ともにゼロとなる [M(0)=0 W(0)=0] 。そして、W(t) 及び M(t) の値は、以下のようになる。

  • { M(t) = ( 1-e^{-(D+B)t} ) \frac{C}{D+B} }
  • { W(t) = C \frac{B-K}{D+B}(\frac{1}{D+B+F}e^{-(D+B)t} - ( \frac{1}{D+B+F} - \frac{1}{F} ) e^{Ft} - \frac{1}{F} ) }
結論と所見
  • 死亡した魔法少女は、魔女にならない。キュゥべえの目的は、魔法少女が魔女に変化するときに解放されるエネルギーを回収することである。よって魔女になることなく死んでしまう魔法少女などは、キュゥべえからみれば、契約獲得に要したコストの無意味な浪費でしかない。であるから、魔法少女の死亡率を下げるために、魔女を魔法少女より弱くなるようこのシステムを設計することが、彼にとっては重要となる [B > D] 。
  • また、魔法少女が魔女より強ければ、大部分の魔法少女は、魔女に変化するまでに、複数の魔女を倒すことになる [K - B > 0] 。であるから、魔女の供給源として、(魔法少女以外に)使い魔という存在が必要になってくる。本来なら、キュゥべえの目的は、魔法少女から魔女を生成することのみであるにもかかわらず、魔女のもう一つの生成源として、わざわざ使い魔というものをこのシステムに導入したのも、以上のことが理由であると考えられる。
  • 魔法少女の個体数の初期値や、C, D, B といった各値に、どのような数値を代入したとしても、魔法少女の個体数は、つねに C / (D + B) の均衡に到達するだろう。周囲に魔法少女が少ない理由は、おそらくキュゥべえの契約獲得能力の制限によるものだろう。
  • たとえ、魔法少女の個体数の初期値に、極めて大きな数値を設定したとしても、彼女たちの個体数は急速に減少し、その個体数を維持できるレベル C/ (D + B) に達して均衡するだろう。これは別段、個体数を極めて多くしたことからくる魔法少女個々体へのストレスや、環境への過大なストレスなどのせいではない。線形増加率から導かれる当然の帰結である。
  • 一方、魔女の個体数はけして均衡に達しない。まずこのシステムを稼働した初期においては、魔女の個体数はきわめて短時間に、不規則に変化していくだろう。しかし必然的に、使い魔の増殖 [eFt] が、他のあらゆる変数を追い越し、やがて魔女の個体数を指数関数的に増加させることになる。魔女の爆発的な増殖は、人間の個体数の減少をもたらし、おそらくはその人間の減少によってのみ、魔女の増殖は抑制されることになるだろう。だが、人間の個体数がそこまで減少すれば、我々がこのモデルに対して最初に導入した前提条件もまた、くずれさる。
  • 以上のことから、この魔法少女-魔女のシステムが導入された惑星は、必然的に死滅すると結論づけることができる。もはや人間の滅亡は回避できない。魔女及び魔法少女を一体残らず滅ぼせば話は別であるが。おそらく、キュゥべえが地球において持続可能なやりかたでエネルギーの回収を行おうとせずに、エネルギーを回収できるだけ回収した時点で、あっさりと地球から去っていこうとしたのも、以上のことから説明がつく。

より精巧なモデル

 上記のシンプルモデルの欠陥は、魔女の個体数がいくら増減しても、魔法少女が倒せる魔女の個体数に変化が生じないと仮定したことにある。この仮定のために、シンプルモデルを用いたシミュレーションは、実際の状況とは異なるふるまいをみせることになるだろう。よって我々は、シンプルモデルを以下のように変更することにした。

  • まず、魔法少女は魔女と1対1で戦うと仮定する。
  • つぎに、魔法少女の個体数が魔女の個体数より圧倒的に少ない場合は、魔法少女たちの大部分は毎回、魔女との戦いにおもむくことになるだろうし、それ以外の状況下では、彼女たちは休息するか、前回の戦いで受けた傷の治療に専念するか、あるいはごく普通に日常生活を送るだろうと仮定した。
  • つまり魔法少女の個体数が魔女の個体数より多ければ、一部の魔法少女のみが魔女と戦うので、その回における魔法少女の死亡確率 D や、魔法少女が魔女に変化する確率 B は低下する。つまり、D 及び B の値は、魔女の個体数に応じて変化する。
  • 魔法少女が魔女に変化するのは、魔法を使ったときのみであると仮定する。(つまり魔法を使わないかぎり、ソウルジェムは濁らないと仮定する) また、魔法少女が魔法を使うのは、魔女と戦うときのみであると仮定する。(つまり、魔法少女どうしで戦いあったりはしないと仮定する)
  • 時間 t = 0 のとき(第0回)にも、最低でも魔女が一体は存在するものとしてシミュレーションを行なった。これは、そのように設定しなければ、上記の仮定からなるこの精巧モデルにおいては t がいくら増大しようと、魔法少女がいつまでも魔女に変化しないためである。もし魔女の個体数を0でシミュレーションを行えば、W(t) は常に0であるし、M(t) がグラフに示すカーブは直線になる。
モデル

 さて、あとは以上のことがらを数式に置き換えるだけである。

  • 定数の値は、前回のシンプルモデルと同じものを使うことにする。
  • 魔女との戦いにおもむく魔法少女の比率をあらわすために P という定数を導入する。(具体的に説明すると、たとえば P=0.8 と設定するならば、毎回、20% の魔法少女は戦いにおもむかずに休息をとることになる)
  • このシステムは以下のようになる。
    • { \Delta M(t) = C - P \times (D + B) \times \min{(W(t), M(t))} }
    • { \Delta W(t) = F \times W(t) + (B - K) \times P \times \min{(W(t), M(t))} }
シミュレーション

 この式を Matlab にて実行した結果が、以下のグラフである。

 グラフは、「より精巧なモデル」における個体数の増減。

 このシミュレーションで用いた値は、以下の通り。

  • C = 100
  • D = 0.25
  • B = 0.125
  • F = 0.015
  • K = 0.12
  • P = 0.8

 前節のシンプルモデルと同じく、実際には、これらの値は不正確なものであると思われるが、これらの値を変更したとしても、出力されるグラフは似通った形状になるだろう。

二つのモデルの比較。

 このより精巧なモデルにおいては、個体数の増減は、以下の二つの特徴的な期間をたどることになる。

  • まずはじめの頃は、魔法少女の個体数が、ほぼ直線的に増大する。この時期には、ほとんど魔女が存在しないために、魔法少女の死亡率も、ほぼゼロである。そしてキュゥべえは一定のペースで少女たちと契約し魔法少女を生産していく。ゆえに、魔法少女の個体数は、ほぼ直線の一定の率で増加することになる。戦いもほとんど発生しないので、ソウルジェムもゆっくりとにごるし、魔女化する魔法少女も同じく少ない。それゆえに、魔女の個体数の増加も、シンプルモデルにくらべると、ゆっくりと進行する。
  • しかし、ある時期(このシミュレーションにおいては t=300 )を迎えると、この状況は大きくかわる。魔女の個体数が、システムにとって、きわめて重要な要素へと変化する。つまり、魔女との交戦機会が増えることによって、魔法少女はさまざまな影響を受けることになる。このために魔法少女の個体数の増加は緩慢になり、やがて減少に転じる。逆に、魔女の個体数は、加速度的に増加していく。つまり、最終的に魔法少女の個体数は C/(D+B) の均衡状態に向かい、魔女の個体数は、指数関数的な増大へと向かうことになる。

 はじめに示したシンプルモデルでは、このより精巧なモデルによって示されたような二段階からなる増減の過程を明らかにすることはできなかったし、魔法少女の個体数も、精巧なモデルより早い時期に均衡状態に達することになった。しかしながら、シンプルモデルの回数をどんどん進めていくと、やがては、より精巧なモデルでの第二段階にみられる魔女と魔法少女の増減グラフと一致した結果が得られる。したがって、シンプルモデルによって得られた結論は、以上の精巧モデルからみても妥当なものであると判断される。ただしその時期は、シンプルモデルで予測されるよりも未来に、おとずれることになるが。

Matlab Scripts

 上記、二つのモデルで用いた Matlab スクリプトは、以下で入手できる。