あまりアクセントを気にしない日本語

日本語のアクセントってどれぐらい意味あるのかなって。
以前から気になってたんだが、別にこんなこと気にしなくても通じるのよね。
地域によってバラバラなだけならわかるのだが、人によっても少し違うというのだから困る。
そうなると気にする意味がないよねって。
まぁ自分になじみのあるように言ってくれれば聞きやすいけど、
そうじゃなくても何を言ってるのかよく聞けば何の問題もない。


橋・端・箸というのは有名な同音異義語だが、
これをアクセントで区別しようなんてのは無茶だと思う。
調べると、京阪式アクセントでは、
橋は は を高く言う、端は はし を高く言う、箸は し を高くしていうらしい。
確かに言われてみればそうですね。
じゃあ日本語ならどこでも大体こんな感じなのかというとそんなことはないらしい。
東京式アクセントでは、
橋は し を高く言う、端と箸は同じで は を高く言う。
あれ?逆転してるし。まぁ有名な2つのアクセント比較しただけでこの有様だ。
これを見ると本当に信用してはいけないなと思いますね。
ちなみに奈良県の南部は東京式アクセントを使ってたりするらしいです。不思議な話ですけどね。
そう考えると山を1つ超えただけでアクセントが逆転してるとかそういうこともあるかもしれない。


むしろ日本の方言が恐ろしいのは語彙の方でしょうかね。
普通に使ってる言葉が通じないとかあるみたいですね。
とごる という言葉があるんですね。これは底にたまるという意味。
僕は何の違和感もなく使っているけど、なんか使われている範囲はそうも広くないらしい。
概ね、三重県・愛知県・和歌山県の範囲で使われているらしい。
一体何のことかわからない範囲ですが、大体これと一致するらしいです。
まぁ1つの言葉だけなら驚くことではないかも知れないが、こんなのが山ほどあるんだから恐ろしい。
言葉の意味で通じなくなってしまうことはあるかもしれませんね。


日本の方言の違いはヨーロッパの言語の違いに匹敵する、
なんてことも言われてますが、実際はわりと通じてしまうものです。
ということはヨーロッパの言語の違いってその程度なのか。
元々同じゲルマン人の言葉だった英語とドイツ語なんてかなり違いますからね。
けどもしかしたらそういう違いなのかも知れません。
あれも単語の置き換えして一部言い回しを変えればわかってしまうのかもしれません。
そう考えるとえらいもんですね。

コードを埋め込んだ全知全能の正規表現

11の倍数を発見する正規表現というのは書けるだろうか。
そんなもんいるのかというのは置いておいて、
作れるかどうかでいうと、Perlでは案外簡単に作れてしまいます。
えらいもんですね。


まずパターン中にコードを埋め込めるんですね。

m/(?{print "X"})/

何のことかわからないが、とにかく(?{~})の中は実行されると言うこと。
これを使ってやります。といってもこれだけでは駄目なんですが。
これとこういうパターンを使います。
(?(条件)真の時|偽の時)という風なパターンがあるんですね。
条件は(?{~})を使ってコードで書けると。
それともう1つ使えばできます。それは(?!~)ってパターンです。
これは~の文字列を含まない文字列というパターン。
実は含まない文字列のパターンってむちゃくちゃめんどくさいんですよ。
けどこれを使えば簡単なんですね。(?:(?!foo).)+とかするだけです。
これの~部分に何も入れない(?!)ってパターンは何にもマッチしないという意味らしい。
これらを使って(?(?{$1%11==0})|(?!))っていうのを$1をキャプチャするところの直後に入れると、
$1%11==0を満たさないとき(?!)ってパターンが入ってドボン。
という仕掛けになってるわけだ。
というわけでこれを使って11の倍数を検索してみた。

print join(',' , '22597619494114889245'=~m/(\d+?)(?(?{$1%11==0})|(?!))/g),"\n";

すると、22 , 597619 , 94114889と出てきます。うまいこといってますね。
この程度なら極端に遅いなんて事もありません。


これの使い道ですが、例えばIPv4のIPアドレスにマッチするするパターンとかね。

m/(\d{1,3})(?(?{$1<256})|(?!))\.(\d{1,3})(?(?{$2<256})|(?!))\.(\d{1,3})(?(?{$3<256})|(?!))\.(\d{1,3})(?(?{$4<256})|(?!))/

各桁の数字は1桁から3桁だけど、3桁は3桁でも256未満ということでこういう風に条件を埋め込めると。
もちろんこの場合はガリガリ書けば256未満のパターンをこんなの使わなくても作れますけどね。
けど意味がわかりにくい。けどこれなら一目瞭然ですよね。
いや知ってればですけど。けど$1<256っていう制約があるんだなってわかりそうなもんだ。
こういう正規表現あってもいいと思ってたけど、さすがPerlやってくれますね。


Perlのマッチはかなり柔軟だと思います。
PHPでは置換でコールバック関数使うときは特別の置き換え関数を使ったけど、
Perlの場合はeオプションつけるだけですからね。
さすがだなと思いますけど、これPerl以外で通用するのか?
それだけが心配なところです。Rubyではそのまま打っても通用しなかったからね。
また調べてみることにしよう。

C++ならメモ化不要の力業で

Project EulerのProblem 31はおもしろい問題だと思う。
これは200ペンスをコインの組み合わせで表すとき何通りの方法があるかという問題です。
コンピュータにこれを数えさせるんですね。


これをそのまま解くとネタバレになりそうなので、これをちょっと改題して話題とする。
3800円を、2000円札・1000円札・500円玉・100円玉・50円玉・10円玉・5円玉・1円玉であらわす方法は何通りあるか、
という問題にする。まぁ基本的には一緒ですね。
これをC++で解いてみた。

#include <iostream>
class MakeYen{
private:
static int _yens[9];
long _FindWays(int yen,int step){
if(step==sizeof(MakeYen::_yens)/sizeof(*MakeYen::_yens)) return 1;
int curyen=yen;
int ways=0;
while(curyen>=0){
ways+=this->_FindWays( curyen,step+1 );
curyen-=MakeYen::_yens[step];
}
return ways;
}
public:
long FindWays(int yen){
return this->_FindWays(yen,1);
}
};
int MakeYen::_yens[]={10000,5000,2000,1000,500,100,50,10,5};
void main(void){
MakeYen maker;
std::cout << maker.FindWays(3800) << std::endl;
}

わざわざMakeYenクラスなんて用意してますが、これはこの先の話で使うから。
とりあえずFindWaysメソッドを使えば求まると。
まず貨幣の種類をMakeYen::_yensに収めてある。1円玉は抜けているけど、これは理由がある。
実際の計算をおこなう_FindWaysメソッドは、yenとstepという2つの引数を取るが、
yenは金額、stepは_yens[step]以下の貨幣を使って、その金額を表す通り数を返すと。
計算方法は例えば278円を100円以下の貨幣を使って表す通り数は、
278円を50円以下で表す通り数+178円を50円以下で表す通り数+78円を50円以下で表す通り数
として求めることができるんですね。確かにそうなんですよ。
これを再帰的に施していけば求まると。そういう寸法です。
そして最後に1円以下で表す通り数は必ず1通りです。だから1円玉がリストに入ってないの。
if(step==sizeof(MakeYen::_yens)/sizeof(*MakeYen::_yens)) return 1;なんて書いてあるけど、
これはMakeYen::_yensの長さとstepが等しくなったらという意味。


さて、とりあえずこれで求めてみようと。
するとすぐには出てこないで、1秒ぐらいかかって出てくる。
ネイティブなコードが生成されるからこの程度で済んでるけど、Perlで同じ事をやるとすごく時間がかかる。
まぁそういう問題です。
じゃあPerlで解けるのかというと解けます。
メモ化という手法を使ったんですけどね。
メモ化とは例えば、278円を100円以下の貨幣を使って表す通り数はなんぼか聞かれたら、
1回目はきちんと計算して、その結果をメモして、2回目以降はメモした値を言うと。
そういうことです。実はこの問題はこれでかなり計算時間が削減できます。
まさに二度あることは三度ある。というわけでこれにあわせて多少修正。

#include <map>
class MakeYen{
private:
std::map<std::pair<int,int>,long> _cache;
static int _yens[9];
long _FindWays(int yen,int step){
if(step==sizeof(MakeYen::_yens)/sizeof(*MakeYen::_yens)) return 1;
std::map<std::pair<int,int>,long>::iterator cur=this->_cache.find(std::pair<int,int>(yen,step));
if(cur!=this->_cache.end()) return cur->second;
int curyen=yen;
int ways=0;
while(curyen>=0){
ways+=this->_FindWays( curyen,step+1 );
curyen-=MakeYen::_yens[step];
}
this->_cache.insert(std::pair<std::pair<int,int>,long>(std::pair<int,int>(yen,step),ways));
return ways;
}
public:
long FindWays(int yen){
return this->_FindWays(yen,1);
}
};

std::map<std::pair<int,int>,long>なんて用意します。これはstd::pair<int,int>をキーとして、longの値を記録するもの。
ハッシュといいたいのだが、どうもハッシュじゃないらしいです。
まぁいいや。キーはstd::pair<int,int>ですが、これはintとintの組ということ。
まず、記録があるか確認する方法は、findメソッドを使います。キーを渡すとイテレータがもらえるんですね。
これがmapの末尾をさしていたらそのキーに対応する値はない。
そうじゃなければその得られたイテレータのさすキー・値ペアがfindで渡したキーに対応するものだと。
ちなみにキー・値ペアもstd::pairを使って表されています。紛らわしいですね。
これで見つかればこれを返して、そうじゃなければまじめに計算すると。
そして最後に引数をキーに求めた結果をセットすると。
insertメソッドにキー・値ペアを渡せばセットされます。
という調子です。いや、扱いにくい。
しかしこれを使えば本当に一瞬で出ます。効果絶大のようです。


しかしネイティブだとあまりに計算が速くてありがたみがないですけどね。
元々Perlでやってたときはこれよりは計算量の少ないProblem 31でもかなり時間を食ってたので、
メモ化してやっと実用的な時間で求まるというような調子でした。
それがネイティブなら力業でできるという情報を検証するためにやってみたんですけどね。
いや、確かに力業でできますわ。
しかしやっぱり一瞬ででないのは残念なので、わざわざメモ化を実装してみました。
けどC++は本当に強力だ。

高橋メソッド専用ツールがあれば簡単ですね

一発目からやたら複雑な実験で本当に困った。
なんか半導体にアルミニウムを蒸着して調べたんだが、
ショットキー接合がなんやらと言われても、残念ながら知らないと。
じっくり調べることにしましょう。


何度か話題にしたことがあるか、ないか知らないけど、
高橋メソッドっていうプレゼンテーションの手法があります。
これは、とにかく大きな字ばっかりで構成されたスライドを出して話をすると。
元々はHTMLで字を出して、これをハイパーリンクかスクロールかで移動していってやってたんじゃないかな。
まぁ簡潔な仕組みではありますよね。
ただ、あんまりにも異端なのでちょっと扱いが難しいかな。
しかしおもしろいツールがあるんですね。
高橋メソッドなプレゼンツール in XUL リターンズ
XULというのはMozillaのUIを表現する言語ですね。
というわけでMozilla Firefoxで動かすのが前提になっています。


takahashi.xulというファイルを開くと、プレゼンテーションになってるんですね。
上の方にカーソルを持って行くとページの前後やペンが使えるようになっている。
驚くほど高性能ですね。
しかも、このファイルのXMLを編集してスライドを作れるんですね。
元々文字だけなので簡潔な仕組みでできるんです。
なので簡潔に書いてやれば、非常に見やすいスライドができると。
これと、あとはくわしい話をしてやれば非常に理解しやすいんじゃないかなと思う。


試しにこれでいろいろ書いていたのですが、
すると大した量も書いていないのに50ページを超えるという恐ろしいことになりました。
高橋メソッドは1枚の情報量が極端に少ないのが特徴ですからね。
このtakahashi.xulの説明スライドも42ページですからね。
そういうものだということです。
正直異端だが、使いやすいツールもあるし、時々活用してみようと思う。

死んだように見えたイヤホンはイヤーピースだけ生き返る

部で僕が作ったメールロボットを改造してたのだが、そうしたらバグって誤爆メールが山ほど送られた。
一応デバッグ時は送らないようにしてたんですけど、その後ミスがありましてね。
ちゃんとチェックしないといけませんね。あと横着しちゃいけませんね。
えらいことをしてしまった。


ところで、以前D-Snapのイヤホンが壊れたんですが、その後別のイヤホンを買ったんですね。
1280円で買えました。JVCのなんですけど、いいイヤホンだとは思います。
前のイヤホン同様にインナーイヤー型のイヤホンなんですけどね。
ただ、イヤーピースの形が悪いのか、どうも耳にうまいことフィットしない。
大中小とあって、小を使ってるんですが、どうも小さすぎる気がする。
だからといって中を使うと、引っかかる感じがして話にならんと。
どうしたもんかなと思って前から気になってたんだが、しかし添付のイヤーピースは3つだけだしなと。


今日、机を見てみると、前のD-Snapのイヤホンが転がってて、久しぶりにつけてみたら、
なかなかいい具合にフィットしてたものだと思ったけど、今はもう使えない。
ただ、イヤーピースは使えるのかなとふと思って外して、これを今のイヤホンにつけてみた。
するとうまいことつく。驚いた。
基本的にはどのメーカーのでも取り替えられるものらしい。
とりあえずこちらの方が僕は好きなので、これをこれから使うことにする。
まぁ白いイヤホンに、D-Snapの黒いイヤホンについてたイヤーピースを移植したから気持ち悪いけど。
しかしこういうのもありだと思います。


ちなみにイヤーピースも形状や材質によって音質が変わるらしいです。
ただ、それ以上にフィットしないのはまずいように思うので、耳にあうというのは重要だと思います。
いや、前のに比べると随分楽なんですね。
もし気に入らんというのなら、いろいろなメーカーの交換用のを試してみるといいでしょうと。
これでしばらく試してみて、よさそうだったらこれからもこのまま使いましょう。

そこにconst、ここにconst、それがC++

boost::asio::ip::tcp::socketを使ってHTTPのリクエストを送り、レスポンスを受け取るメソッドを作ってた。
GETしかできないとか、レスポンスを始めに全部読み取ってしまうとかいう問題はあるけど、
POSTを使わない、大きなデータを扱わない限りは十分実用的と言えるでしょう。
POSTの実現方法自体は簡単だから必要なら用意しただろうが、必要じゃないので省略。
また整理したらここで取り上げようと思う。


ところでC++にはよくわからない文化がある。
というのはconstキーワードのことです。
JavaやC#のユーザーからすればconstキーワードというと定数を作るものだと思うが、C++では必ずしもそうではない。
その意義はJavaのfinalやC#のreadonlyに近い気もするがそれも違う気がする。

#include 
#include
void main(void){
std::vector v(1);
v.at(0)=19;
std::vector v2(0);
const std::vector &vref=v;
//vref=v2;
std::cout << vref.size() << std::endl;
std::cout << vref.at(0) << std::endl;
//vref.at(1)=4;
//vref.clear();
}

constキーワードというのは、そのオブジェクトは変更不可だよということらしい。
const std::vector &のように参照とくっつけて使われることが多いですね。
ちなみにconst std::vector &とstd::vector &は違う型として扱われるので注意。
もちろん変更不可なんだから代入はききませんよと。
けどsizeを呼び出してもちゃんと値を返してくれます。値を変えないメソッドには関係ありません。
しかし、atで取り出したオブジェクトを利用するのは問題ないけど、代入するのは無理。
これはconstをつけたときとつけなかったときでatの性質が変わって、
constつけたときは返す参照もまたconstであると。実はそういうことなんですね。
clearのようなメソッドも封じされてしまいます。


C++を始めて、一番混乱したのがこのconstキーワードのことですね。
C#ではconstキーワードは定数にのみ使うもので、引数にconstとかあり得なかったんですね。
constつけられるといろいろと制約が多いんですね。
そのことを知らなかったものだからコンパイルエラーは大量に出るしえらいことでした。
そもそもconstというのは定数に使うものだと思っていたから、僕は変数の性質がconstであると言っているのだと思っていたんですね。
けど実際はconst型とでも言うべきものだったんですね。これに気付くまで随分と時間がかかりました。
理解してしまえば、自分でこれをうまく応用できるようになりました。


constの使い道ですが、まずメンバ変数の参照を返すときですね。
メンバ変数への参照を渡してしまうと書き換えし放題、だからといって実体を渡すとコピーされて大変。
そこでconst std::vector&のような型を返り値にする。
すると送られるのは参照だからコピー不要、その一方で書き換えは阻止できる。
ただ使い方を知らないと混乱するかもね。受ける方はこうする。

const std::vector &vref=obj.GetRef();

それと引数を参照渡しで受け取る時ですね。
別にconst無くても作る方からすれば大した問題ではないですけど、
const型はconstを外すキャストはできないんですね。逆はできるんですけど。
だからconstな人にために変更しないならconstをつけておいてあげましょう。


自分でクラス作るときもconstの人への配慮を忘れないように。
どうするのかというと自分自身を変えないメソッドにはconstキーワードをつけておきましょう。

class Foo{
public:
int Size(void) const;
//中略
};
int Foo::Size(void) const{
//内容
}

このようにconstをつけます。なんでこんな場所にconstキーワード書くのかはわからないけどつけておきましょう。
そうすれば初めの例のsizeメソッドのようにconstだったとしても使えるようになります。
これも普通に使ってる分には役に立たんだろうと思うかも知れないけど、
constな人からすれば必要なことなんですね。


まぁこんな感じです。これでconstキーワードを活用できるかも。
C#やってた人からすると、スタックにオブジェクトの実体をおいておけることと、constキーワードは衝撃的でしたね。
どちらも扱いめんどくさいと思うのですが、C++では通常GCを使いませんからね。
スタックに実体をおいておけば解放いらんから、その点では有利とは言えるし、
constつけて参照渡しするのだって、C++では参照渡ししなければオブジェクトがコピーされるからで、
このあたりはC++では重要な道具なんですね。
郷に入りては郷に従えという言葉もありますが、C++ではこの流儀に従うことにしましょう。
というわけで適切なところでconstキーワードを使えるようになりましょう。

C++でもJavadocがあればな

JavaにはJavadoc、C#にはXML Documentation、PerlにはPOD、PHPにはPHP Documentationなど、
ソースコードに書いた特別のコメントを抜き取って文書を作るツールがあるわけです。
ところがC++にはそういうのないよなと思って調べていたのですが、
Javadoc同様に書いたら使えるツールがあるということがわかった。
Doxygenというツールです。


まずヘッダーファイルにこんな要領でコメントを書いておきます。

/**
RFC4180に沿ったCSVのストリームを解析し読み進めるパーサーです。
@author Hidemaro
*/
class CSVReader{
private:
//略
public:
/**
ストリームを受け取り初期化するコンストラクタです。
@param from ストリーム
*/
CSVReader(std::istream&);
/**
行末にたどり着いたかを返します。
@return 行末にたどり着いたらtrue
*/
//後略

Javadocは/**から始まり*/で終わるコメントで書くことになっています。それ同様に書きます。
特殊なことは@から始まる行で書くんですね。
@paramは引数について説明を書く、@returnは返り値について説明を書く。
他にもいろいろありますが、特に有益なものを挙げてみた。
こういう風に用意した上でDoxygenを使うわけですね。


Doxygenをインストールして、Doxywizardを起動します。
まず、Step1のディレクトリにソースコード・ヘッダーファイルの並んでいるディレクトリを指定します。
それでStep2のWizardで、Source code directoryになんか書いてあるけど消します。
これでStep1で指定したディレクトリから入力するようになります。
それでProject nameなどを入力。
Nextで進んで、Optimize for C++ outputを選んでおく、
Nextで進んで出力ファイルの種類を選ぶ。ここではHTMLだけ作成することを考えます。
これで基本はOKですが、詳しい設定をExpartのタブで行います。
Projectの項のOUTPUT_LANGUAGEをJapaneseに、
Inputの項のINPUT_ENCODINGをファイルのエンコーディングに合わせる。
Visual StudioではShift_JISを使うのが普通らしく、Shift_JISで作っているので僕はこれをShift_JISに変更した。
設定が終わったら、Runのタブに変えて、Run doxygenをクリック!
これでhtml以下にドキュメントができていると。index.htmlを開くと読める。
結構読みやすいですね。これを読みつつやれば便利でしょうね。
あと、コメントの中に使用例を示すのもいいかもしれませんね。
@codeから初めて@codeendまでの間にかけばいいですね。クラスの説明にでも書いておきましょう。


こういう道具は非常に重要です。
一見なんでもないようなものですが、引数は0から数えた数字だったけ、1から数えた数字だったけ?とか、
そういうときに確認するものがやっぱり欲しいんですよね。だから作ってあげましょう。
もちろんHTMLで出力できるのも便利なのですが、ヘッダファイルに書いてあるというのも便利な話です。
クラス・メソッドを作ったらすぐに書くぐらいの気持ちでいましょう。
まぁDoxygenはそのきっかけとなっただけなのかもしれません。そりゃHTMLで見れたら便利だけどね。

彼は化け物、私も化け物

実は、高専の4年生に編入することができます。
主に工業高校の卒業生向けです。
うちのクラスにも2人の編入生が来ました。
今日その人とじっくり話す機会があったので思ったこととか書いておこう。


しかし2年間だけ勉強しに来たというのも中途半端な話ですよね。
それなら高専を卒業して大学に編入しても大学にいるのは2年だからそれも中途半端だが、
しかし大学院に行こうと思えば、どこかで2年間の勉強が必要だから大学に行くというのが実のところだろう。
ただ、工業高校の人にとって都合がいいのは今まで勉強してきたことがすぐ生きることですね。
それで聞けば、実践をしたかったから高専に来たというようなことを言ってくれた。
なるほど、そういう意図なら悪くないのかも知れない。
しかし狭き門ですけどね。欠けた人数を埋めるぐらいの人数しか取りませんから。


ところで、なんでこんな話になったのかというと、
思うところがあって、第三種電気主任技術者の試験を受けようかなと思って図書館に調べにいったんですね。
なぜか通称は電験三種。まぁあまり気にしてはいけません。
これは、理論・機械・電力・法規の4科目の試験からなっていて、各科目6割以上の得点を取って合格して免状をもらえば、
50,000V未満の事業用電気工作物の工事、維持及び運用に関する保安の監督を行うことができるということ。
ちょっと話題に出てくることがあってね。
それでそこで会ったわけですが、聞けば彼は電験三種に合格しているんですね。
さらに恐ろしい事に電験二種の一次試験の3科目に合格しているという。
なんだこれはと思って聞けば、学校で重点的にやっていたらしい。
しかしそれにしてもすごい人です。まさかここまでとは思わなかった。
そのために他のことがおろそかになっているかもとは言っていたね。
例えば化学の授業が極端に少なかったり。はっきりわからないみたいだったが1単位とか言ってたな。


一方で、君たちは3年間でどんな恐ろしい事を学んできたんだとも言われた。
高専というのは5年間でやることをやるわけですが、
そうなると話のつながり上、少々難しくても低い学年でやることがあるんですね。
僕もどこが違うのかというのは詳しく知らないのですが、
教科書に工業高校の電気基礎の科目の教科書のクローンがあるのでこれと照らし合わせてみよう。
例えば電磁気について言うと、2年生でこんな事をやったわけである。
まずクーロンの法則が出てきて、力とか電界のことをガリガリやるわけだ、
そうしたら次にガウスの法則が出てきて、これでいろいろな電界を計算していくと。
そしたらここで積分の勉強をして、この求めた電界を積分して電位・電位差を求めて、
それでコンデンサの容量を求めたりしたわけだ。
これで2年生の分終わり。この後あと半年電界を勉強して、最後の半年で次回をやったわけです。
しかし、持っている教科書によればこうである。
まずクーロンの法則が出てくる点は一緒だが、次にいきなり電位が出てくる。
しかし電位は積分して求めたわけじゃなくて公式が載ってると。
随分違いますね。その次にコンデンサが出てくるわけだが、これは形状ごとに公式が載ってるだけ。
実際彼らがどんな勉強をしてきたのかは知らないけど、この通りだとすれば、確かに恐ろしい事かも知れない。
ただ電磁気は2・3年生で勉強したら、特別には勉強しません。そりゃ後に復習するかもしれないけどね。
だから、彼は明らかに大学でやることだろ、と言うわけだけど、2年・3年でやるほかにやるところがないわけですね。
まぁ実にそういうことです。


まぁこんな具合でおもしろい話を聞けたわけだ。
電験三種にいかにして合格したかとかいう話も聞きつつ、ぼちぼちがんばってみようかなと思ったわけだ。
まぁ実は卒業して実務経験を積んだら第二種電気主任技術者の免状がもらえて、
場合によってはそれで事が足りることもあるそうだが、免状をもらうことが重要なわけではない。
いや重要は重要ですけどね。
何より腕試しでしょう。彼もそれにはまったと言っていた。
とりあえずぼちぼちチェックしていこう。と思って、とりあえず機械の本を借りてみた。

うまいことCSVを読み込むC++

CSVの解析を機能はSAXのようにメソッドで投げてやろうかとおもったが、
それは結構扱いが悪いことがわかった。
.NET FrameworkにはSAXってないんですね。その代わりにSystem.Xml.XmlReaderってのがあるんですよ。
これは自分で読み進めていくXMLパーサーなんですね。
SAXより扱いがよいよと説明されていて、確かに考えてみるとこれには説得力があると思った。
これは結構便利なんじゃないかと思ってこれをまねてCSVReaderを作った。


というかなんでC#ではCSVを配列の配列に変換するものを作っておきながら、
C++になったらそんな風に読み進めていくやつを作っているのかと言えば、
C++のvectorにvectorを入れると取り扱いが悪いからですね。なので使う方が賢く読んでデータに落としてくれと。
そういうことです。例えば1行分を1つのオブジェクトにして、これの配列を作るとかなら一旦配列の配列に変換するより便利だよねということ。
作ったわけですが、あまりの大作でここに貼ることができないので、別に置いておく。
csvreader.tbz (804Bytes)
804Bytesってえらい少ないな。元々3.24KBもあったんですけどね。恐るべしBZip2。
ここに貼れない理由はあと1つあって、C++だからヘッダファイルとソースファイルをセットで置かないとなと思ったから。


さて、使い方ですが、ストリームを与えてコンストラクタでオブジェクトを作成する。
そして各フィールドをReadFieldメソッドで読み込む。行末についたかはEndOfRowメソッドで確認する。
ただ行末についてもReadFieldメソッドは空白を返し続ける。表計算に消されたけど実際はあるんだよというのもあるかもしれないから。
それで次の行に進むためにGoToEndOfRowメソッドを動かす。行末についていなくても使える。
それで全部読み終わったかどうかはEndOfCSVメソッドで確認できる。
そんなわけで例を2つ書く。まずはあるセルを全部読み込んで表示するプログラム。

#include "CSVReader.h"
#include
#include
#include
void main(){
try{
std::ifstream csvfile("..\\test.csv",std::ios::binary);
if(!csvfile) throw std::exception("File open error!!");
CSVReader mycsv(csvfile);
while(!mycsv.EndOfCSV()){
while(!mycsv.EndOfRow()){
std::cout<<">"<< mycsv.ReadField() <<"< ";
}
mycsv.GoToEndOfRow();
std::cout<< "/" < }
}catch(std::exception &e){
std::cout << "\nException : " << e.what() << std::endl;
}
}

行の最後まで読んで次の行に進むから、mycsv.EndOfRow()がtrueになるまで読み続けて、
終わったらGoToEndOfRow()で次の行にすすむという調子でやっています。
いろいろ置いて試してみるといいですが、一応RFC 4180に沿ったものはうまいこと読めるはずです。
RFC4180では、内容にカンマ・CR・LF・ダブルクオートが入る場合はダブルクオートで囲み、
ダブルクオートは2つ連ねてエスケープするということになっています。
foo”Bar”と書きたければ、”foo””Bar”””のように記載するのがよいとされています。
まぁカンマがあればダブルクオートで囲むというのは浸透しているが、
ダブルクオートがあればダブルクオートで囲むというのはあんまり浸透していない気がする。
そこで、最初がダブルクオートから始まらなければ、ダブルクオートだろうが書いてあるまま解釈するようにしてある。
すなわち、単にfoo”Bar”と書いてあってもfoo”Bar”と解釈してくれる。
けど”Foo”barはダブルクオートから始まってるからうまいこと解釈しないで例外を投げる。
あと改行コードはCR+LFじゃなくてもLFでも失敗しないようにしてある。
そうそう、WindowsがCR+LFをLFに勝手に変換するから、混乱を避けるために開くときバイナリモードにしてる。
Windows以外ではこれが当然なんですけどWindowsだけはこういう問題ありますから。


さて、もう1つ例を挙げる。これは1行目の1つ目に列数が書いてあり、2列目以降に内容があるCSVを考える。
ただ行によっては無駄な列があったり、列が足らなかったりする。

3,,,,,,,,,,,,,,
イ,ロ,ハ
ニ,ホ
へ,ト,チ,リ

ニから始まる行が3列目がないわけですが、実は空白でした。
というわけでこれを解釈するプログラム。

void main(){
try{
std::ifstream csvfile("..\\test.csv",std::ios::binary);
if(!csvfile) throw std::exception("File open error!!");
CSVReader mycsv(csvfile);
int columns=boost::lexical_cast(mycsv.ReadField());
mycsv.GoToEndOfRow();
while(!mycsv.EndOfCSV()){
for(int i=0;i if(i!=0) std::cout << " - ";
std::cout << mycsv.ReadField();
}
mycsv.GoToEndOfRow();
std::cout< }
}catch(std::exception &e){
std::cout << "\nException : " << e.what() << std::endl;
}
}

boost::lexical_castですが、くわしくはletsboost::lexical_castを見てください。
まぁこれがなければcstdlibをincludeしてstd::atoiを使えばいいかなという気はするけど。
解釈すると多い分は読み飛ばして、足りない分は空白で補ってくれます。
OKですね。


ソースコードを見てみると、かなりひどいことになっています。
RFC4180に書かれていることを忠実に実現しようとするとかなり複雑になるんですね。
ただカンマで区切ればいいというものでもないんです。
しかし実際こんなに厳密にCSVを考える必要はそう多くないですけどね。
ただ後で知らなかったとならないように質のいいものを用意しておこうと。そういうことです。
もしかしたらなんか抜けがあるかもしれないけど、これでよさそうですね。
必要に応じて活用してください。
ただできるだけRFC4180に沿ったCSVを投げ込んでくださいね。
多少は外れたのを許容してるけど、だからといって意図したとおり解釈するとは限らないわけだから。
あとは…逆にCSVをうまいこと書き出すやつが欲しいか。
けど書き出す分にはダブルクオートがない限りはなんでもかんでもダブルクオートで囲めばいいからなぁ。
楽なもんです。

テンプレートに甘えるC++でなしにインターフェースを使う

C++でCSVを解釈しようと思ったのですが、いろいろ考えた結果、SAXのように処理することにした。
SAXはXMLパーサーのAPIの1つ。Simple API for XMLの略なのですが、確かにシンプルですね。
オブジェクトを設定しておいて、文書を読み込んだら、まずオブジェクトのstartDocumentメソッドを呼び出して、
開始タグを発見したらstartElementメソッド、テキストを発見したらcharactersメソッド、終了タグを発見したらendElementメソッド、最後にendDocumentメソッド。
という風にメソッドを呼び出して読み込んでいくわけ。
これは何の役に立つんだという話ですが、XML文書から必要な部分だけオブジェクトに落とすときに使います。
複雑な処理はできないけど、例えばfoo要素の子のname要素・addr要素の値を得たいとか、
そういうことを処理するのにはちょうどいいのかもしれない。
複雑な処理をしたければXML文書を一回読み込んで、それをツリー状にして処理できるDOM使え。


さて、そういうわけで読み取るオブジェクトのためのインターフェースを作ろうと思った。
やはり使うメソッドをインターフェースで定めて、これを実装していただこうと。
しかしC++にはインターフェースってないんですね。
ただしC++は多重継承できるので、クラスでインターフェース風のものを作ります。

class ICSVCatchable{
public:
virtual void BeginCSV(void) =0;
virtual void BeginRow(void) =0;
virtual void NewCell(std::string data) =0;
virtual void EndRow(void) =0;
virtual void EndCSV(void) =0;
};

virtualをつければ仮想メソッドになります。これでオーバーライドできるわけです。
まずこれは必要なのですが、これでは宣言したメソッドを定義しなくてはならなくなる。
そこで最後に=0をつけることで定義をしなくてよくなる。
C#で言うと、抽象メソッドと呼ばれるやつですね。あれは、継承された先で専らオーバーライドされるメソッドですから。
まぁそんなところです。


しかしJavaやC#のインターフェースとはほど遠いものであるのも確かです。
というのもなぜかICSVCatchableのインスタンスが作れてしまうんですね。
まぁあまり気にしてはいけませんね。
JavaやC#では性質ごとにインターフェースを用意して、必要に応じて実装するということが普通です。
例えばC#で繰り返し可能なオブジェクトはIEnumerableを実装するとかね。
ただC++ではそんな習慣がないんですよ。
繰り返し可能なオブジェクトはbeginとendのメソッドでイテレータがもらえるものだという程度。
そのイテレータも、++・*・!=の演算子をオーバーロードしているかというぐらいのものです。
こういうのダックタイピングというんですけどね。
なんでC++でこんなことができるのかといえばテンプレートを多用しているから。
けどテンプレート絡みのエラーはとても読みにくい。それを考えるとあんまり便利とは言えない気がする。
やはりこういうインターフェースを使う方がわかりやすいし、うまくやりやすいと思う。


そんなものいらんだろうという話もありますが、
しかし多重継承ではこういうことも予定されてますからね。
工夫してインターフェースを実現すれば使い道は多いと思います。
引数の型にICSVCatchableとか書くだけでいいんだからね。便利便利。