やあ子どもたち。秋だね秋がやってきたね。日増しに寒くなっていくこの季節、風邪などひかないように気をつけてくれよな。インフルの予防注射も受けとけ周りに迷惑かけないためにもな。今日は実数値の入ったSTLのvectorの内容の正規化(規格化とも言うかな)、、まあvectorでなくてlistでもqueueでも何でも良いのだけど、についてのお話だよ。
C++をしばらくやっていなかったり、眠かったり、もう歳だったりすると、うっかりダメなコードを書いてしまい、「やべ俺もう終わった」みたいなことになるので、気をつけたい。以下のコードを見てくれ。
vector< double > vec; for( int i=0; i<10; ++i ) vec.push_back( -(i-5)*(i-5)+27 ); // 正の実数vector内容の正規化を行います。 #if 0 // 敗北コード auto it_max = std::max_element( vec.begin(), vec.end() ); for( auto& ival: vec ) { ival /= *it_max; }// ival #else // 合格コード auto max_val = *std::max_element( vec.begin(), vec.end() ); for( auto& ival: vec ) { ival /= max_val; }// ival #endif for( auto& ival: vec ) { cout << ival <<", "; }// ival cout <<endl;
話しを簡単にするために正の実数の入ったvector< double >を考えるよ。ここでは、最大値が1になるように全要素の大きさを適切にスケーリングしようとしているよ。
「敗北コード」と「合格コード」の出力の違いを予測できるかな?
そうだね、前者では、最大値が途中で現れた時点でそれは1になるから、最大値よりも後にある残りの要素は1で割り算されるのでそのままの値となる。つまり正規化は最初の要素からその最大値の要素のとこまでしか行われない。まあ深刻なバグだなこれは。かたや「合格」コードでは、最大値の値そのものを使って割り算しているのでそのような問題は発生せず、全要素の正規化は正しく行われるはずだ。
さて、「敗北コード」が怖いのは、最大値が常に最後の値になるような実数列についてはうまく動いてしまうから、バグに気付けないということなんだ。こんなところにも自分でコードを起こすことの怖さが潜んでいるね。
もしこんなコードを書いてる奴が自分以外にいたとしたら、おじさんならこう言うに決まってる。
「いいかこんなものはテスト済の自分ライブラリとして持っとけ、勿論どんなコンテナがきてもいいように関数テンプレートとでもしてな。そもそもそんなものは標準関数の中にないのか?探したのか?(あと、このような目的でstd::min/max_element を使う際は必要でない限り、戻りのイテレータではなく、すぐにそれが参照している「値として」取り出して利用することを心掛けようね。。)」と。
なので一応、「正の実数double の入ったコンテナの中身の正規化を行う関数テンプレート」を作ったのでメモしておくぞ。
template< class Container > void NormalizePositiveFloatContainer( Container& cont ) { if( cont.empty() ) //←thanks to RiSKさん return; auto max_val = *std::max_element( cont.begin(), cont.end() ); for( auto& ival: cont ) { ival /= max_val; }// ival return; }
今回は若い頭脳明晰な君たちには物足りない内容だったかもしれないが、おじさんはもう若くないのさ。今これを読んでるみんなよりも歳とってたりするのさ。そうするとね、年寄りは間違うんだよそしてそれは誰だって仕方のないことなんだ。だからこそのテンプレートライブラリの公開であり、クラウド上のメモなんであるわけなんだよね。
じゃあ今日はこの辺で!