Subscribed unsubscribe Subscribe Subscribe

sci

最果て風呂

mto を C++ で実装してみる

C++ での実装ができた。手間はあまり変わらなくて、実行速度は C よりも若干速い程度だった。大きいファイルの読み込みでベンチマークをしていないので、誤差の範囲かもしれないけれど。

今まで実装してきたものと大きく変わるのが class を使うという概念。Objective-C で何となくやっていたことを真似すれば良いみたい。用語は違うのだろうけど、interfaceimplementation という名前を付けて思い出しながら作業。

設計

クラスの設計というと大袈裟なのだけど、メインとなるのは内部辞書になる。それとこれに関連する辞書のサイズ。array 型を使ったので size() で取得できるかな?と思ったのだけれど、これは宣言の部分に指定した配列の大きさ(今回は 4500)になるのね。自動的に大きさを変更できる array の使い方もあるみたいだけれど、理解できなかったので C でやっていた方法で辞書の大きさを得ることにした。これらはデータ。

メソッドについては変換対象のファイルを検索・置換させるためのものが 2 つ、あとは辞書の大きさを表示するものが 1 つ、という感じ。インスタンス?を作った時に自動的に初期化することができるとのことなので、それに関するもの(だと思う)を 1 つ。

環境変数の取得

実装の順番としては C を参考にしてまずは環境変数の取得からはじめた。環境変数の取得は他の言語と同じで getenv("") を使えば簡単に得ることが出来た。string 型を使って、要望する文字列にしたくて + を使ったのだけれど連結がうまくいかなかった。そこでいちいち新しい変数を作って後で結合するという方法にした。ここはもうちょい調べないといけない。

ファイルの読み込み

次はファイルの読み込み。ifstreamgetline を使って一行ずつ読み込むことができた。一気に読み込む方法を調べてみたけれど、char 型を使った C 言語っぽくなるようなので調べるのを止めた。エラー処理は Golang みたいだなと思った。

正規表現

内部辞書を作成する部分。正規表現は簡単に使うことができた。ペアにして配列に放り込む部分は sscanf を使ってやっていたのだけれどうまくいかず、substrfind を使ってなんとか作ることができた。この辺は Python3 とかと比べるとまだ面倒だね。

引数処理

部品が揃ったので読み込ませるファイルを決め打ちにして実験。abortsegmentation fault を何度も出しながら実装を進める。似たようなことを何度もやっているけれど、言語によって微妙に違うというのが面白い。C++ は面倒だわ。

決め打ち方法でうまくできたので引数処理に取りかかる。ここは C の部分を丸っとコピペ。変更したのは putsstd::cout にしたくらいかな?argc3 を期待している時の挙動が C とは違っておかしい(abort する) ので変更。まだ完全に解決できていないけれど、これで良しとする。

テスト

すべての実装が終り、Makefile でテストに追加して実行してみたら tradkanamodernkana オプション、つまり仮名遣い変換の部分が NG になってしまった。どうやらサンプル内の「ている」が変換されていないみたい。

追ってみると s/ている/てゐる/ 状態だったみたい。s/ている/てゐる/g となるように修正するとテストが OK となった。

// 修正前
while (getline(infile, str)) {
  for (int i = 0; i < elemSize; ++i) {
    std::string::size_type index(str.find(innerDict[i][0]));
    if (index != std::string::npos) {
      str.replace(str.find(innerDict[i][0]),
                  innerDict[i][0].size(),
                  innerDict[i][1]);
    }
  }
  std::cout << str.data() << std::endl;
}
// 修正後
while (getline(infile, str)) {
  for (int i = 0; i < elemSize; ++i) {
    std::string::size_type index(str.find(innerDict[i][0]));
    while (index != std::string::npos) {
      str.replace(str.find(innerDict[i][0]),
                  innerDict[i][0].size(),
                  innerDict[i][1]);
      index = str.find(innerDict[i][0], index + innerDict[i][1].size());
    }
  }
  std::cout << str.data() << std::endl;
}

感想

実装してみての感想。namespace というものがあって、何だか C# みたいな感じがした。使おうとする関数というかメソッドというかに std:: を付けないといけない。スコープの問題(仕様)なのだろうけど。今回は using namespace std; を文頭に書かずに、いちいち std:: を付けることにした。まぁもう C++ を書くことは無いだろう。

C# みたいとか Golang みたいとか書いてきたけど、元は C++ の方が先なんだろうね。自分の歩んできた道がそうなんだから仕方ないやね。どの言語も Hello, World! 的な部分しか触っていなので、もうちょい複雑そうなものを作ってみたいのだけれど、必要とするものが無い……。種々のアルゴリズムを勉強して速度を改善するのも良さそうだけれど、やっぱりそこまで必要ないな。

ベンチマーク

新しい言語での実装が追加されたので例によってベンチマークをしてみる。平均を計算するのが面倒なので最速のものを採用する。

1 2 3 最速
C 0.127 0.124 0.123 0.123
CC 0.092 0.092 0.094 0.092
CCL 11.723 - - 11.723
Go 0.257 0.263 0.263 0.263
C#(mono) 0.163 0.165 0.165 0.163
ObjC 1.405 1.415 1.413 1.405
SBCL 4.095 4.126 4.104 4.095
node 0.260 0.111 0.111 0.111
Lua 1.156 1.151 1.171 1.151
Lua-JIT 0.892 0.879 0.866 0.866
Perl5 0.088 0.093 0.087 0.087
Python3 0.083 0.082 0.078 0.078
Ruby 0.156 0.145 0.144 0.144
mruby 0.757 0.727 0.733 0.727
Gauche 3.078 2.360 2.184 2.184

f:id:nakinor:20160323201917j:plain (CCL は除いてある)