問題3.5より。
英単語をハイフンで分ける方法のごく一部を考えましょう。 … 与えられた単語をハイフンで区切る関数をどのように書けばよいでしょうか。
なんとなく解き方はすぐ頭にイメージできました。でもコードにするのが一苦労でした。世の中のたいていのことは、思ったよりも難しいものなのです。
作戦
まず、入力の全行を逆転させておいて、そこから木をつくれば探索にはO(logn)しかかからないんじゃないかと考えました。
逆転
入力の全行を逆転させるために、Rubyでワンライナーを書きます。
ruby -p -l -e '$_.reverse!' hyphen.list > hyphen.reversed
in:
et-ic
d-ic
s-tic
i-ac
....
out:
ci-te
ci-d
cit-s
ca-i
----
木
各ノードに[文字, ハイフンの位置, 子供のリスト] を持たせた木を考えます。
でもこれだと探索するときに子供を線形探索することになってしまうなぁ。
コード
1 // ------------------------------------------------------------ 2 // Hyphnate tree 3 // 4 class Node; 5 6 bool hasChar(const char ch, const Node& n); 7 8 class Node { 9 public: 10 char ch; 11 int pos; // position of hyphen from tail 12 list<Node> children; 13 14 Node(char c, int p = -1) : ch(c), pos(p) {}; 15 16 Node* add(const char c) { 17 // XXX: linear search 18 list<Node>::iterator found = find_if( 19 children.begin(), children.end(), 20 boost::bind( ptr_fun(hasChar), c, _1)); 21 22 if (found == children.end()) { // not found 23 // cout << "Node::add : " << c << endl; 24 Node n(c); 25 children.push_front(n); 26 return &children.front(); 27 } 28 29 return &*found; 30 } 31 32 }; // class Node 33 34 bool hasChar(const char ch, const Node& n) { 35 return n.ch == ch; 36 } 37 38 39 Node root('c'); 40 41 void addNode(string& pf) { 42 Node *n = &root; 43 int pos = -1; 44 for (string::iterator i = pf.begin() + 1; i != pf.end(); i++) { 45 if ('-' == *i) { 46 pos = i - pf.begin() -1; 47 continue; 48 } 49 n = n->add(*i); 50 } 51 n->pos = pos; 52 } 53 54 string hyphenate(string& str) { 55 Node *n = &root; 56 string ret = str; 57 int pos = -1; 58 59 for (string::reverse_iterator i = str.rbegin()+1; i != str.rend(); i++) { 60 // cout << "HY: " << *i << endl; 61 if (n->children.empty()) { 62 // cout << "hyphenate : " << n->pos << endl; 63 pos = n->pos; 64 break; 65 } 66 67 list<Node>::iterator found = find_if( 68 n->children.begin(), n->children.end(), 69 boost::bind( ptr_fun(hasChar), *i, _1)); 70 if (found == n->children.end()) { // not found 71 if (n->pos != -1) { 72 pos = n->pos; 73 break; 74 } 75 cout << "hyphenate : not found" << endl; 76 return ""; 77 } 78 79 n = &*found; 80 } 81 82 cout << pos << " " << ret.size() << endl; 83 // ret.resize(ret.length() + 1); 84 ret.insert(ret.begin() + ret.length() - pos - 1, '-'); 85 86 return ret; 87 } 88 89 // ------------------------------------------------------------ 90 // utility 91 // 92 void showTree_rec(Node *n, int indent) { 93 for(list<Node>::iterator i = n->children.begin(); 94 i != n->children.end(); 95 i++) { 96 for (int j=0; j<indent; j++) { 97 cout << " "; 98 } 99 cout << i->ch << " "; 100 if (i->children.empty() || i->pos != -1) { 101 cout << i->pos; 102 } 103 cout << endl; 104 105 106 showTree_rec(&*i, indent + 3); 107 } 108 } 109 110 111 void showTree() { 112 showTree_rec(&root, 0); 113 } 114 115 // ------------------------------------------------------------ 116 // test 117 // 118 int main(int argc, char **argv) { 119 assert(argc == 2); 120 121 // build tree 122 string postfix; 123 ifstream ifs(argv[1]); 124 while (!ifs.eof()) { 125 ifs >> postfix; 126 addNode(postfix); 127 } 128 129 showTree(); 130 131 // hyphenate 132 string input; 133 while (true) { 134 // read line 135 cin >> input; 136 137 if (cin.eof()) { 138 break; 139 } 140 141 cout << hyphenate(input) << endl; 142 } 143 144 return 0; 145 }
実行例
a
i 1
m 2
i
a 1
b 2
d 1
f 1
h 1
l 1
c 3
b 2
l 2
m 1
n 1
h 2
t
a 1
c 2
n 2
a 2
o 1
y
l 5
p 2
s 2
i
l
a 5
e 1
c
palistic
pa-listic
actic
ac-tic
mantic
man-tic
gigantic
gigan-tic
critic
hyphenate : not found
所感
できたーと思って問題文をもう一度見返してみたら、ハイフンは一カ所だけとは限らないと。 このコードはハイフンが一カ所しかないことを前提にしているので、正しくないです。otz
辞書ファイルを入力として、すべてのアナグラムを見つけるプログラムを書きます。 辞書ファイルには一行につき一単語が書かれてあるものとします。
その1
sign, sort はそのままなので省略します。squashをどう書くか。
一番始めの処理と、アナグラムを格納するコンテナがいるかなと思って書いたコードが次のものです。
1 int main() { 2 string key, val; 3 string prev; 4 set<string> grp; 5 6 // first time 7 cin >> key >> val; 8 9 prev = key; 10 grp.insert(val); 11 12 do { 13 // read line 14 cin >> key >> val; 15 16 if (prev == key) { 17 grp.insert(val); 18 } else { 19 // print anagrams 20 for (set<string>::iterator i = grp.begin(); i != grp.end(); i++) { 21 cout << *i << " "; 22 } 23 cout << endl; 24 25 grp.clear(); 26 27 prev = key; 28 grp.insert(val); 29 } 30 } while (!cin.eof()); 31 32 // finish 33 34 if (!grp.empty()) { 35 // print anagrams 36 for (set<string>::iterator i = grp.begin(); i != grp.end(); i++) { 37 cout << *i << ", "; 38 } 39 cout << endl; 40 } 41 42 return 0; 43 } 44
とりあえず、出力は正しいことを確認できました。でもなんだかきれいなコードではないように見えます。
その2
2.8の解答を読んで、なるほどコンテナは必要ないのだということに気づいて書き直したのが次のものです。
1 int main() { 2 string key, val; 3 string prev = ""; 4 5 while (true) { 6 // read line 7 cin >> key >> val; 8 9 if (cin.eof()) { 10 break; 11 } 12 13 if (key != prev && "" != prev) { 14 cout << endl; 15 } 16 17 cout << val << " "; 18 19 prev = key; 20 } 21 22 // finish 23 cout << endl; 24 25 return 0; 26 }
だいぶすっきりしました。
所感
やはり先人のコードというのは簡潔で美しいものだと思います。見習わねば。
ex 2-8より。
n要素の実数の配列と実数tと整数kが与えられたとき、要素がk個の部分配列で、要素の和がtを超えないようなものがあるかどうか、すばやく判定するにはどうしたらよいでしょうか。
3分考えてアルゴリズムをひねり出してみました。
部分配列の和をincrementalに求める作戦
部分配列をずらしながら、和をincrementalに求めれば、速く計算できるのではないかと考えました。 計算量は O(n) で収まるはず。
コード
1 bool subarr_sum_less(VI &a, int t, int k) { 2 // init 3 int sum = 0; 4 for (int i=0; i<k; i++) { 5 sum += a[i]; 6 } 7 8 if (sum < t) { 9 return true; 10 } 11 12 for (int i=1; i<a.size()-k+1; i++) { 13 sum -= a[i-1]; 14 sum += a[i+k-1]; 15 16 if (sum < t) { 17 return true; 18 } 19 } 20 21 return false; 22 }
実行例
% ./ex2-8 100 -100 3
-19, -97, 212, -161, -245, 181, 114, 121, 123, 118, 229, -284, -12, 213, 276, -90, -67,
-200, -36, -130, -62, -185, 56, -69, -235, 177, -101, -197, 270, 242, 141, 97, -272,
292, 111, -93, 186, 102, -36, -173, 111, 240, -68, 78, 0, -89, 104, 101, -236, -188,
-216, 24, -11, 96, -102, -123, -123, -230, 253, -282, -80, 146, -9, -13, -93, -96, -235,
267, 0, 98, 136, -120, 37, 151, -110, 47, -256, -74, -245, 15, -41, 151, 284, 274, -233,
-191, -47, 80, -165, -127, 168, 217, -172, -194, -183, 155, 72, -252, 228, -93, 1
=> 1
結果はあってそうです。
所感
解答を読んだのですが、なんか全然やり方違う… 部分配列って、元の配列の連続した部分列のことじゃないのかな?
2.5 問題より。
配列の回転はabという配列をbaに変えます。abcをcbaにするにはどうしたらよいでしょう。(これは大きさの違うメモリブロックの交換の問題です。)
2つ書いてみました。
memmove
ぱっと思いつくのは、a,cのいずれかを一時的に退避しておいてbをmemmoveするというもの。
- a, cのうち、サイズの大きい方(L)を一時バッファに置いて
- bをmemmove
- a, cのうち、サイズの小さい方(S)をLの方にmemcpy
- LをSの方にmemcpy
1 /* 2 * abc -> cba 3 * requires extra memory 4 * 5 * left : start of b 6 * right : end of b 7 */ 8 void swap_abc(VI& a, int left, int right) { 9 int size_l = left; 10 int size_r = a.size() - right - 1; 11 12 if (size_l > size_r) { 13 VI tmp(size_l); 14 copy(a.begin(), a.begin() + left, tmp.begin()); 15 16 // memmove 17 int pos = size_r; 18 for (int i=left; i<right+1; i++) { 19 a[pos] = a[i]; 20 pos++; 21 } 22 23 copy(a.begin() + right+1, a.end(), a.begin()); 24 copy(tmp.begin(), tmp.end(), a.begin() + size_r + right - left + 1); 25 } else { 26 VI tmp(size_r); 27 copy(a.begin() + right + 1, a.end(), tmp.begin()); 28 29 // memmove 30 int pos = size_r + right - left; 31 for (int i=right; i>=left; i--) { 32 a[pos] = a[i]; 33 pos--; 34 } 35 36 copy(a.begin(), a.begin() + left, a.begin() + size_r + right - left + 1); 37 copy(tmp.begin(), tmp.end(), a.begin()); 38 } 39 }
しかしこれではメモリを余分に使ってしまいます。ではどうするか。
reverse
ProgrammingPearls::配列のrotateのエレガントな解法を使えばできるんじゃないかと思いました。
- reverseを部分的に行って
- 全体をreverse
ためしに紙で手書きしながらやってみたら、うまくいきました。
1 /* 2 * abc -> cba 3 * by rotation 4 * 5 * left : start of b 6 * right : end of b 7 */ 8 void swap_abc_reverse(VI& a, int left, int right) { 9 reverse(a.begin(), a.begin() + left); 10 reverse(a.begin() + left, a.begin() + right+1); 11 reverse(a.begin() + right+1, a.end()); 12 reverse(a.begin(), a.end()); 13 }
できました。
所感
memmoveする方は、コード化もややこしいし余分にメモリも食います。 reverseの方は、コードにするのも簡単ですし、効率も良いといういいことづくめに見えます。
書く前にアルゴリズムをよく考えてみることが、結局はバグの少なくて速いコードを書く秘訣なのだとわかります。
8 . アルゴリズムデザインのテクニックより。
n要素の配列xを入力とし、xのすべての部分配列の和のうち、最大のものを出力する
で紹介されている5つのアルゴリズムを実装してみました。
自分で考えてみる
- 配列には正と負の値 (とゼロ) の2種類のものしか入っていないので、それぞれをまとめたら何かうまい方法が見つかるんじゃないかと思ってやってみました。これにかかる時間は、O(n) です。
- 次に、小さくなった配列について、隣同士の和を求めたら、何かの役に立つのではないかと思って和の配列をつくってみました。これにかかる時間も、 O(n) になります。定数項は元の配列のサイズ n よりもだいぶ小さくなっているはず。
- さて、これからどうしたものか…
けっきょく問題を小さくしただけで、解くためには元の問題と同じアルゴリズムを使う必要があるのではないでしょうか。otz
O(n^3)
いちばん単純なアルゴリズム。愚直にすべての部分和を先頭から求めて最大のものを探します。
1 int max_subarr_O3(VI &array) { 2 int n = array.size(); 3 int maxsofar = 0; 4 5 for (int i=0; i<n; i++) { 6 for (int j=i; j<n; j++) { 7 int sum = 0; 8 9 for (int k=i; k<=j; k++) { 10 sum += array[k]; 11 } 12 maxsofar = max(sum, maxsofar); 13 } 14 } 15 16 return maxsofar; 17 }
実装で悩んだのは、i, j, kの範囲で境界を入れるかどうかというところ。
O(n^2)
O(n^3) のアルゴリズムで、部分和をキャッシュするもの。
1 int max_subarr_O2_sum_cache(VI &array) { 2 int n = array.size(); 3 int maxsofar = 0; 4 5 for (int i=0; i<n; i++) { 6 int sum = 0; 7 8 for (int j=i; j<n; j++) { 9 sum += array[j]; 10 11 maxsofar = max(sum, maxsofar); 12 } 13 } 14 15 return maxsofar; 16 }
部分和を求めるループを外に出しておくもの:
1 int max_subarr_O2_precalc(VI &array) { 2 int n = array.size(); 3 int maxsofar = 0; 4 5 VI cumarr(n+1); 6 cumarr[0] = 0; 7 8 for (int i=0; i<n; i++) { 9 cumarr[i+1] = cumarr[i] + array[i]; 10 } 11 12 for (int i=0; i<n; i++) { 13 for (int j=i; j<n; j++) { 14 int sum = cumarr[j+1] - cumarr[i]; 15 16 maxsofar = max(sum, maxsofar); 17 } 18 } 19 20 return maxsofar; 21 }
いずれも境界をループに含めるかどうかを迷ってしまいました。
O(n logn)
分割統治戦略で。これはちょっと考えつきにくいなぁ。
1 int max3(int a, int b, int c) { 2 int m = max(a, b); 3 return max(m, c); 4 } 5 6 // devide-conquer and O(n log(n) ) 7 int max_subarr_Ologn_rec(VI &array, int l, int r) { 8 // no elements 9 if (l > r) { 10 return 0; 11 } 12 // one element 13 if (l == r) { 14 return array[l] > 0 ? array[l] : 0; 15 } 16 17 int m = (l+r)/2; 18 int sum, lmax, rmax; 19 sum = lmax = rmax = 0; 20 21 for (int i = m; i>=l; i--) { 22 sum += array[i]; 23 lmax = max(sum, lmax); 24 } 25 26 sum = 0; 27 for (int i=m+1; i<r; i++) { 28 sum += array[i]; 29 rmax = max(sum, rmax); 30 } 31 32 return max3(lmax+rmax, max_subarr_Ologn_rec(array, l, m), max_subarr_Ologn_rec(array, m+1, r)); 33 }
2分探索みたいな書き方になります。2分探索もそうだけど、境界の扱いに気を使います。
O(n)
とどめは、 O(n) のアルゴリズム。コードも一番シンプルになります。
1 int max_subarr_On_scan(VI &array) { 2 int maxsofar = 0; 3 int maxendinghere = 0; 4 5 for (size_t i=0; i<array.size(); i++) { 6 maxendinghere = max(maxendinghere + array[i], 0); 7 maxsofar = max(maxendinghere, maxsofar); 8 } 9 10 return maxsofar; 11 }
境界も難しくありません。でもこれは思いつけない…
所感
O(n logn) くらいまでは自力で導きたいなぁと。 境界を入れるかどうかは、ループを書くときにいつも悩むところです。 きちんとした方法を編み出さないとです。
実行時間
g++のコンパイルオプションを -O3 -fast -mcpu=7450 -g にして実行時間を計ってみました。単位はμ秒です。
n 100 1000 10000
-----------------------------------------
simple 1881 1375914 540120837
sum cache 93 5552 566072
pre calc 71 8267 597084
divide conq 25 236 2747
scan 16 34 306
歴然とした差になりました。 O()記法って便利だなぁ。
ProgrammingPearls::配列のrotate の続き。
トリッキーなお手玉 アルゴリズムがやっと実装できました。
コード
1 // tricky rotation, requires O(n) 2 void rotate_tricky(VI& array, size_t x) { 3 int tmp = array[0]; 4 size_t size = array.size(); 5 size_t prev = 0; // index of element to be stored 6 size_t next = 0; // index of element to store 7 size_t offset = 0; 8 9 for (size_t i = 0; i < size; i++) { 10 assert(i >= 0 && i < size); 11 12 prev = next; 13 next += x; 14 15 if (next >= size) { 16 next = next % size; 17 } 18 19 if (offset == next) { 20 array[prev] = tmp; 21 22 next = ++offset; 23 tmp = array[next]; 24 } 25 else { 26 array[prev] = array[next]; 27 } 28 } 29 }
所感
19 if (offset == next) {
を、
19 if (0 == next) {
としていて、そこのバグがなかなかとれませんでした。
0にしていたのは本に書いてあったからなのですが、それが適用されるのは初期の場合だけということに気づきませんでした。
どうやって気づいたかというと、テストで失敗したときに実行過程をダンプさせ、手で確認し、期待される振る舞いと比較したら、「あぁ」となったのでした。
実行時間
実装できた3つのアルゴリズムについて、 10000個の要素を持つ配列を3つ左に回転させたときの実行時間を計ってみました。単位はμ秒です。
- NI (simple) : 33749
- TRICKY (お手玉) : 7259
- REVERSE (手のひらを返す) : 4725
STLのstd::reverseを使ったコードが一番速かったのでした。
巻末の解答にもありますが、要素を移動する回数が少ないからお手玉の方がREVERSEよりも速いんじゃないの? と思いがちです。 実際にはお手玉はメモリをとびとびにアクセスすることになり、キャッシュを活かしきれないため、性能が出ないのだろうということでした。なるほど。
本当は、
- N = [10,10000)、i = [0, N) で実行時間を計測
- 結果をRubyでCSVに変換
- NeoOfficeでグラフ化
とするつもりでした。 1と2はできたのですが、3のグラフ化がうまくいきませんでした。NeoOfficeはPowerBook 12″には負荷が高すぎるようで…
Programming Pearls 2.1.Bより。
要素がn個ある配列を左方向にi要素分回転させるにはどうすればよいでしょう。… 単純には、n個の作業用配列を作ってnステップで仕事をすればよいのですが、メモリを数十バイトしか使わずに実行時間もnに比例するだけのように回転させられますか。
をやってみます。
その1
一番単純なやり方。実装も素直で間違えませんでした。
1 // simple rotation, but requires O(n x i) 2 void rotate_ni(VI& array, size_t x) { 3 int tmp; 4 size_t size = array.size(); 5 6 for (size_t i = 0; i < x; i++) { 7 // left edge 8 tmp = array[0]; 9 10 for (size_t j=0; j < size-1; j++) { 11 array[j] = array[j+1]; 12 } 13 14 array[size-1] = tmp; 15 } 16 } 17
その2
難しい… まだテストが通りません。 あとで書きます。
その3
これは思いつかないなぁと。 でも確かにプログラムはとてもシンプル。しかも効率が良いというすごいものです。
1 void rotate_reverse(VI& array, size_t x) { 2 reverse(array.begin(), array.begin() + x); 3 reverse(array.begin() + x, array.end()); 4 reverse(array.begin(), array.end()); 5 }
所感
問題を解くときには、いろいろな見方をすることが大切だということが良くわかります。まずは安直に書いてみて、それをどう改善するかというアプローチもいいと思いますが、その3みたいに全然違う方向から光を当てないと出てこないようなアイデアもあるし。
そのためには、やっぱりたくさんの問題を自力で解くことで、引き出しを増やしていくしかないのかなと思います。
街で噂のVoxを始めてみました。 blog + SNS みたいなものなのかな? いたずらに書く場所が増えても仕方ないので、面白いかどうか様子を見ながらやっていこうと思います。
そういえば今やってるのって何があるかな。アクティブなものだけ数えると、
- ここ
- はてな
- 会社
とかかな。 書くもの以外でよく使うWebサービスというと、
- Google Reader
- Flickr
- はてぶ
- Last.fm
- mixi (日記は書いてない)
くらいかな。最近の人からすると、けっこう少ないのかも。
最近コードを貼り付けることが多くなってきました。 単にpreタグでコピーしてただけなので、とてもみづらかったのです。
なんとかしようと思い、ふとVimには2htmlなんてプラグインがあったなぁと思い、探してみたら 名無しのVIM使い::2html.vim なんていう記事があり、ふむふむと。
このblogはTypo on Railsなので、Rubyから2html.vimを使いたいなぁと思い、再び探してみると、Code Snippets::Using VIM as a syntax highlighting engine from Ruby なんてものがあり、うまく使えないかなといろいろやってみました。
Code Snippetsのものをそのままコピペして実行してみたところ、作成したTempfileを開くところでpermission deniedになってしまいました。#{expr}でevalしているところで刺さっているようです。
んー、といろいろやってみたところ、system()にしてみたらうまくいきました。
コード
1 require 'tempfile' 2 3 def vimsyn(text, filetype) 4 synfile = Tempfile.new('synfile') 5 synfile.close 6 7 codefile = Tempfile.new('codefile') 8 codefile << text 9 codefile.close 10 11 # vim = '/Applications/Vim.app/Contents/MacOS/Vim' 12 vim = '/usr/bin/vim' 13 14 system(vim, '-f', '-n', '-X', '-e', '-s', 15 "-c", "set filetype=#{filetype}", 16 "-c", "syntax on", 17 "-c", "set background=dark", 18 "-c", "let html_use_css=1", 19 "-c", "let html_number_lines=1", 20 "-c", "run syntax/2html.vim", 21 "-c", "wq! #{synfile.path}", 22 "-c", "q", 23 "#{codefile.path}" 24 ) 25 html = IO.readlines(synfile.path).join 26 body = html.match(/<body.*</body>/m)[0] 27 css = html.match(/<style.*</style>/m)[0] 28 css.gsub!(/pre/,'pre.code') 29 css.gsub!(/^body.*$/,'') 30 body.gsub!(/^<body/,'<div') 31 body.gsub!(/</body>/,'<div>') 32 body.gsub!(/<pre>/,'<pre class=code>') 33 return css+body 34 end
systemに渡す引数がえらいことになっています。ugly hackだなぁ。 あとはうまくまとめてRailsのプラグインにでも仕立てあげたいところです。
これでblogにコードを載せやすくなりました。 オリジナルの作者さんに感謝です。
2.1 (A) の補題 p.p.15 (訳注):
0から15までのどれかの整数が10個あるとき、この10個の中に登場しない整数を1つ見つける
を解いてみます。
アルゴリズム
p.p.15 の本文中ほどに書かれてあるものを素直に実装してみました。といっても、正しいらしいものに至るまで1時間ほどもかかってしまって… otz
1 /* 2 * find a non-existant element 3 * 4 * Programming Pearls 2.1 (a) 5 * $Id: find_nonexistant.cc 493 2006-10-25 15:16:07Z takayama $ 6 */ 7 8 #include <iostream> 9 #include <string> 10 #include <cassert> 11 #include <vector> 12 #include <set> 13 #include <algorithm> 14 #include <cstdlib> 15 #include <cassert> 16 #include <ctime> 17 18 using namespace std; 19 20 typedef vector<int> VI; 21 typedef set<int> SI; 22 23 void print(int n) { 24 cout << n << ", "; 25 } 26 27 int find_nonexistant(const VI& array, size_t l, size_t r) { 28 29 VI less, greater; 30 int m = (l+r)/2; 31 32 cout << "================================================" << endl; 33 cout << "(l, r, m) = (" << l << ", " << r << ", " << m << ")" << endl; 34 for_each(array.begin(), array.end(), print); 35 cout << endl; 36 37 if (array.empty()) { // found 38 return m; 39 } 40 41 for (VI::const_iterator i = array.begin(); i != array.end(); i++) { 42 if (*i < m) { 43 less.push_back(*i); 44 } else { 45 greater.push_back(*i); 46 } 47 } 48 49 cout << "less : "; 50 for_each(less.begin(), less.end(), print); 51 cout << endl; 52 53 cout << "greater : "; 54 for_each(greater.begin(), greater.end(), print); 55 cout << endl; 56 57 if (less.size() < greater.size()) { 58 return find_nonexistant(less, l, m); 59 } 60 return find_nonexistant(greater, m, r); 61 } 62 63 64 // ------------------------------------------------------------------- 65 // test 66 // 67 int main(int argc, char **argv) { 68 assert(3 == argc); 69 70 size_t N = atoi(argv[1]); 71 size_t M = atoi(argv[2]); 72 73 SI int_set; 74 VI array; 75 76 ::srandom(::time(NULL)); 77 78 // make N random numbers from [0..M) 79 while (N != int_set.size()) { 80 int_set.insert(random() % M); 81 } 82 83 // set -> vector 84 for (SI::iterator si = int_set.begin(); si != int_set.end(); si++) { 85 array.push_back(*si); 86 } 87 88 random_shuffle(array.begin(), array.end()); 89 90 // find non-existant number 91 int result = find_nonexistant(array, 0, M); 92 93 cout << result << endl; 94 95 // check result 96 VI::iterator found = find(array.begin(), array.end(), result); 97 assert(found == array.end()); 98 99 return found == array.end(); 100 }
実行結果
% ./find_nonexistant 10 15
================================================
(l, r, m) = (0, 15, 7)
11, 5, 2, 14, 7, 4, 10, 1, 13, 6,
less : 5, 2, 4, 1, 6,
greater : 11, 14, 7, 10, 13,
================================================
(l, r, m) = (7, 15, 11)
11, 14, 7, 10, 13,
less : 7, 10,
greater : 11, 14, 13,
================================================
(l, r, m) = (7, 11, 9)
7, 10,
less : 7,
greater : 10,
================================================
(l, r, m) = (9, 11, 10)
10,
less :
greater : 10,
================================================
(l, r, m) = (9, 10, 9)
9
所感
終了条件が何なのかを掴むのに時間がかかりました。というか、未だにこれでちゃんと終わるかどうか定かではないのですが。



日本語
English