2015-10-12-01-STL書籍の掲載コードをCL-STLで書いてみよう-8 - project-enigma

2015-10-12-01-STL書籍の掲載コードをCL-STLで書いてみよう-8

>> Site top >> weblog >> 月別アーカイブ >> 2015年10月のlog >> 2015-10-12-01-STL書籍の掲載コードをCL-STLで書いてみよう-8

最終更新日付:2015/10/12 22:40:00


STL書籍の掲載コードをCL-STLで書いてみよう-8

2015 年 10 月 12 日

この一連の記事を書いていると、どういうわけだか CL-STL のバグにでくわすことが多い。そうなると、なんだかテストをやっているような気分になる。まぁこれまでのテストが不十分だって話だけどな。引き続き Effective STL、今回は第5項の「単一要素メンバ関数より範囲メンバ関数を使おう」から。

最初のお題はこうだ。

で、答えはこうだった。

v1.assign( v2.begin() + v2.size() / 2, v2.end() );

 

まぁ第5項のタイトルからも明らかな通り、「単一要素メンバ関数より範囲メンバ関数を使おう」という話だ。書籍ではその理由について効率性、明示的ループが不要であることなど色々挙げている。で、これが CL-STL でどう表現されるか。こうだ。

(stl:assign v1 (_+ (stl:begin v2) (/ (stl:size v2) 2)) (stl:end v2))

 

v2 のサイズが奇数の場合はうまく動かないけれど、ここでは重要でない(というか重要視しない)ので割愛。コードとして動作させようとすれば、以下のようになる。

(let ((v1 (new stl:vector 10 0))
      (v2 (new stl:vector #{0 1 2 3 4 5 6 7 8 9})))
  (stl:assign v1 (_+ (stl:begin v2) (/ (stl:size v2) 2)) (stl:end v2))
  (stl:shrink-to-fit v1)
  (stl:data v1))

;=> #(5 6 7 8 9)

 

さて、それじゃあループで書いたらどうなるのよ、という話になるのだが、書籍では以下のようにされていた。いつも通り、面倒な変数宣言は auto に置き換えさせてもらっている。

vector<Widget> v1, v2;
...
v1.clear();
for( auto ci = v2.begin() + v2.end() / 2; ci != v2.end(); ++ci )
    v1.push_back( *ci );

 

昔から、これに違和感があるんだよな。ループの終了条件チェックで ci != v2.end() てしてるやつ。毎回反復子生成するのかよ! と思うのだが、これは時と場合によっては必要になる。つまり、反復子が無効化されうるようなループの場合だ。しかしそうでない場合でもこのような(毎回 end() を取るような)ループは良くみかけるから、低コストの定数時間処理でスタック上にオブジェクトを生成するだけの end() を気にする人は少ないのかもしれない。

しかし、CL-STL ではもっとシビアに考えた方がいいだろう。というのも、スタック上にオブジェクトを作成できず、毎回アロケーションが発生するからだ。そんなわけで、以下のようなループになる。

(let ((v1 (new stl:vector 10 0))
      (v2 (new stl:vector #{0 1 2 3 4 5 6 7 8 9})))
  (stl:clear v1)
  (stl:for (((itr1 (_+ (stl:begin v2) (/ (stl:size v2) 2)))
             (itr2 (stl:end v2)))
            (_/= itr1 itr2) (progn (_++ itr1)))
    (stl:push-back v1 (_* itr1)))
  (stl:shrink-to-fit v1)
  (stl:data v1))

 

では次。ループを使わない別の方法、という話で提示された以下のコード。

v1.clear();
std::copy( v2.begin() + v2.end() / 2, v2.end(), std::back_inserter( v1 ) );

 

これだと最初の clear 呼び出しと合わせてみないと「v1 を丸ごと変更する」のが伝わらないのが問題だね。まぁこれも一応 CL-STL のコードを示しておこう。

(let ((v1 (new stl:vector 10 0))
      (v2 (new stl:vector #{0 1 2 3 4 5 6 7 8 9})))
  (stl:clear v1)
  (stl:copy (_+ (stl:begin v2) (/ (stl:size v2) 2)) (stl:end v2) (stl:back-inserter v1))
  (stl:shrink-to-fit v1)
  (stl:data v1))

 

で、これが「範囲形式の insert」に置き換えられますよ、と。以下のように。

v1.insert( v1.end(), v2.begin() + v2.size() / 2, v2.end() );

 

同じことをやる CL-STL のコードは以下の通りだ。まぁご想像どおり。相変わらず v2 の要素数が偶数じゃないと動作しないが。

(let ((v1 (new stl:vector 10 0))
      (v2 (new stl:vector #{0 1 2 3 4 5 6 7 8 9})))
  (stl:clear v1)
  (stl:insert v1 (stl:end v1)
              (_+ (stl:begin v2) (/ (stl:size v2) 2)) (stl:end v2))
  (stl:shrink-to-fit v1)
  (stl:data v1))

 

‥‥‥正直退屈になってきたのでこれくらいで切り上げよう。次回以降、目先を変えるためにアルゴリズムの項をやってみようかな。

 

このシリーズのこれまでのエントリ

 

コメント

このページにコメントする

 

このページのタグ

Page tag : STLとその移植

Page tag : Common Lisp

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.