[obsolete] stringの実装(vc,g++)と扱い方

この記事は陳腐化しました。C++11 から Copy on Write は実装禁止になりました。g++4.5 以降でも消えました。但し、終端文字を必要とする仕様は健在です。

(2013/08/25 追記)

std::stringを参照渡しにすると高速化した。何を言っているのか(ry

なんて記事を見ると改めてstringの扱いはハマるとこ多いよなあと感じる。結論から言うと可能な限りconst&やiterator-pairを使えばいいんですが。

stringは一つの文字列プールを指す文字列クラスで、vectorと次の点で似ています。

  • 連続したアドレス上に保管される
  • メモリは自動で管理される(大抵は一定量から倍々)
  • 要素に’\0’(NULL文字)を使用可能

但し、その実装は処理系によって結構違います。細かいところまでは読んでいませんが、大まかにvcとgccの実装の特徴を紹介します。

g++4.3.4

実装の特徴は、

  • 格納する文字列に必要なプールは常にアロケータから確保
  • プールにCopy on Write(CoW)の実装

stringのコピーの際に文字列プールを共有(参照カウント)して不要な複製をコストを削減しています。以下捕捉です。

  • sizeof(string)がプールへのポインタ一つ分なので引数の受け渡しなどで有利
  • 非constの添字アクセスはほんのちょっと余計な処理が入るかも
  • 空文字列は専用のプールが用意されており領域の確保は起きない
  • 文字列定数は必ずコピーされる
  • 常に終端文字が入っている

実際に変更しなくても、非constで要素にアクセスしたときプール全体のコピーが発生するので、コピーを回避するにはconst必須です。

VC2010

実装の特徴は、

  • 終端文字を含めて16B以下はstringのインスタンス内に入る
  • それ以上はアロケータから確保

16B以下(15文字/wstringなら7文字)の文字列の生成や結合が高速です。短い文字列を大量に扱う場合特に効果を発揮します。以下捕捉です。

  • sizeof(string)は16B+3ポインタ分と大きめ(Debugビルドでは+1ポインタ)
  • 文字列定数は必ずコピーされる
  • 常に終端文字が入っている

主観的な意見

コピーを回避したいならconst&やshared_ptrを使うので、CoWは余計なお節介だと思います。しかもゲームなど応答性が必要な場合、遅延実行の発動タイミングを意識する必要あります。というか長い文字列を扱う場合は別のクラスを使った方が無難です。個人的にはメモリプールの管理と文字列ポリシーを分けてアダプタで繋ぐような実装が欲しいです。

どちらも常に終端文字が入っていますが、本来C形式である必要はなく、それ故data()とc_str()が用意されています。Cと親和性が必要でない限りc_str()は使わなくていいので、特にvcで16文字格納できないのはちょっと勿体無く感じます。またCの関数にdata()を突っ込んでも動くので誤解される原因でもあります。実際は仕様上の制約でデメリットが大きいため終端文字を付けない実装は見たことがありませんが。

ついでに検査用のソースも載せておきます。でも細かい実装は読んだ方が分かり易いです。

#include <iostream>
#include <string>
using namespace std;
int main() {
	string s1, s2 = "a";

	cout << "size of string: "
	     << sizeof(s1) << endl;
	cout << "initial capacity: "
	     << s1.capacity() << endl;

	while ((char*)&s1 <= s1.data() && s1.data() < (char*)(&s1 + 1))
		s1 += s2;
	cout << "capacity inside of string: "
	     << (int)s1.size()-1 << endl;

	s1 = s2;
	cout << "copy on write: "
	     << (s1.data() == s2.data()) << endl;

	return 0;
}

Codeforces Beta Round #38

おはようございます、昨日の話です。

この前のJAPLJ contestが3ヶ月ぶりのコンテストでコーディング速度が非常に遅くなっていたので、またリハビリしないとなあと思いながら、SRMなんかも時間が合わなくて中々出れなかった中21:00という久しぶりに参加しやすい時間帯だったので参加したら4時間という長時間で非常に疲れました。因みに今回でyellow(Captain)になりました(・∀・)!

Aは極単純,Bは面倒なだけ,Cはリーディングハード,Dもややリーディングハード,Eは普通,F,Gは読んでません。アルゴリズムの難易度についてはとやかく言える身分じゃないので言及しません。

Eの解法について何人もに訊かれたので、書き留めておきます。

Problem E “Let’s Go Rolling!”

http://codeforces.com/contest/38/problem/E

1 \le n \le 3000 \, , \, -10^9 \le x_{1..n},c_{1..n}\le 10^9
直線上にn個のビー玉(質点)をx[i]置いて左(負方向)に転がします。ビー玉はピンで留める事もできて、転がっててきたビー玉もそこで止まります。各ビー玉のコストc[i]は転がった距離またはピン留めコストです。コストの総和を最小化して下さい。

DPです。右方向にビー玉を追加していきます。今n-1個のビー玉があって、n番目のビー玉を一番右に追加したとします。その時増加するコストは、次のようになります。

  • ピン留めする→そのビー玉のピン留めコスト分
  • ピン留めしない→(一番右の)最後にピン留めした位置までの距離

つまり、n-1個までの情報の内、解を求める為に必要な情報は、n-1個の中で最後にピン留めした位置とそのコストです。最後にピン留めした位置が同じならそれ以降の最適解は同じなので、ピン留めした位置毎に最適(最小)な値を残せばいいことになります。
よって、DPテーブルは次のようになります。下方向にビー玉の数n。右方向に最後にピン留めしたビー玉の番号。括弧書きは(x,c)。データは総コスト(解)。青い矢印はピン留めしないときの解。緑の丸はその行の最適解(最小値)。赤い矢印はピン留めしたときの解。最終的な解は最後の行の最小値です。

DPテーブル
DPテーブル

最後にC++のコードです。図の二次元の表で、新しい行を計算するときには直前の一行の値しか参照していないので、一次元配列を更新していくように実装しています。計算量は変わりません。

#include <iostream>
#include <vector>
#include <algorithm>
#define REP(i,n) for(int i = 0; i < (n); ++i)
#define FOR(i,a,b) for(int i = (a); i < (b); ++i)
#define ALL(cont) (cont).begin(), (cont).end()
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef pair<int, int> pii;
typedef vector<pii> vii;

int main() {
	int n;
	cin >> n;
	vii x(n);
	REP(i,n) cin >> x[i].first >> x[i].second;
	sort(ALL(x));
	vl c(1, x[0].second);
	FOR(i,1,n) {
		c.push_back(x[i].second + *min_element(ALL(c)));
		REP(j,i) c[j] += x[i].first - x[j].first;
	}
	cout << *min_element(ALL(c)) << endl;

	return 0;
}

TopCoder Arena plugin moj

mojというTopCoder Arenaのプラグインの紹介です。日本語でこれについて書いてある情報が無かったので書きました。因みにこの記事の内容の殆どは次のフォーラムの引用です。
TopCoder Forums moj (TZTester modification, new features)

mojについて

SRMのソースコードのスケルトンを生成するときに賢いテストコードを挿入します。問題に提示されている全テストケースを瞬時に正確に判定でき、時間を節約できます。
mojはTZTesterの修正版です。TZTester他のプラグインについてはぐぐってください。
機能要約:

  • 出力がかわいくなる
  • doubleの誤差1e-9まで許容
  • 簡単にカスタムテストを作れる
  • Javaと切り替え可能
  • 実行時間測定

他に特筆すべきこと:

  • gcc,VCに対応
  • テストコードがclassの外にあり邪魔にならない

出力はこんな感じにかわいくなります。

Test Case #0... PASSED (relative error 5.44969e-014)
Test Case #1... PASSED (relative error 5.68462e-012)
Test Case #2... PASSED (relative error 4.05691e-008)
Test Case #3... PASSED

使い方

フォーラムの “Download moj” のリンクからzipを落とします。CodeProcessorとFileEditも同梱されています。
基本的にmoj_instructions.txtに説明が書かれていますが、周りのプラグインの入れ方は先程のようにぐぐったほうが詳しく画像付きで説明されていたりするので、その方が間違いもなく楽です。TZTesterとの差分は、次のように読み替えるだけです。

  • TZTester.jar → moj.jar
  • tangentz.TZTester → moj.moj

既に他のプラグインを入れている方は、次の箇所を更新してください。

  • Common ClassPath
  • CodeProcessor Configuration > CodeProcessor Script
  • CodeProcessor Configuration > FileEdit Configuration > Code Template

雛形はzipにcpp,javaファイルがあるので、それに習って書き換えます。