狠狠撸

狠狠撸Share a Scribd company logo
競技プログラミングからみた C++0x OSC 名古屋 2011 8/20 (土) はじめての競技プログラミング 蛇足 @yak_ex /  新 康孝  (CSNagoya)
自己紹介 氏名: 新 康孝  ( あたらし やすたか ) Twitter ID: yak_ex Web: http://yak3.myhome.cx:8080/junks C++ / Perl  が主戦場 現在、仕事でコードに触れていないので 競技プログラミング( TopCoder 、 Codeforces )で 潤い補充 闇の軍団に憧れるただの  C++  好き
C++0x とは 国際標準化機構  ISO  で規格化されている  C++  規格 (ISO 14882) の次期版仮称 200x  年代に発行予定だったが間に合わなかったため  0x  は  16  進プレフィクスということに http://www2.research.att.com/~bs/C++0xFAQ.html 先頃  Final Draft  が承認され規格発行を待つ状態であり正式名称  14882:2011  となる予定 小修正だった  14882:2003  とは異なり、言語自体また標準ライブラリに非常に大きな変更が加えられている 本稿では競技プログラミングから見た  C++0x  の利点について紹介する Codeforces  で  gcc 4.6  での  C++0x  機能が使えるのでそれを基準として説明する
目次 auto Range-based for Lambda tuple initializer_list unordered_map / unordered_set 新規アルゴリズム  (iota, all_of) ちょっとした改善  (map::at)  対応微妙 (regex / emplace)
auto 例えば  set  の要素についてループする場合、 Iterator  の型を書くのが面倒 set<int> s; for( set<int>::iterator  it = s.begin(); it != s.end(); ++it) { if(*it == 5) it = s.erase(it); } set<int> s; for( auto  it = s.begin(); it != s.end(); ++it) { if(*it == 5) it = s.erase(it); } -> auto  にしておけばコンパイラが型を導出してくれる
Range-based for 例えば単純に  set  の要素についてループする場合、 C++03 では色々と面倒 Range-based for  ループですっきり コンテナ  s  の各要素を  v  に割り当ててループ std::set<int> s; for( std::set<int>::iterator it = s.begin(); it != s.end(); ++it ) std::cout << *it << std::endl; std::set<int> s; for( auto   it = s.begin(); it != s.end(); ++it ) std::cout << *it << std::endl; auto  でもやっぱり微妙 set<int> s; for( auto   v : s ) std::cout << v << std::endl;
lambda (無名関数オブジェクト) <algorithm> は便利だけど関数オブジェクト書くのが面倒でループで書いてしまう //  関数オブジェクト struct check2nd { check2nd(const int &n) : n(n) {} bool operator()(const std::pair<int, int> &p) { return p.second == n; } const int &n; }; std::vector<std::pair<int, int>> v; int k; return std::count_if(v.begin(), v.end(), check2nd(k)); //  自前ループ std::vector<std::pair<int, int>> v; int k; int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result;
lambda (無名関数オブジェクト) <algorithm> は便利だけど関数オブジェクト書くのが面倒でループで書いてしまう ↓ その場で関数オブジェクトが書ける スコープ内の変数を参照可能 // lambda vector<std::pair<int, int>> v; int  k ; return std::count_if(v.begin(), v.end(), [&](const std::pair<int, int>& p) { return p.second == k; } ); 中身が  return  だけなら戻り値の型を省略可能 [&](const std::pair<int, int>& p)  -> bool  { return p.second == k; } [&]  はスコープ内変数の参照の仕方の指定
lambda (関数内関数風) ヘルパ関数として切り出したいけど状態渡すのが面倒 int count2nd(const std::vector<std::pair<int, int>> &v, int k) { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; } std::vector<std::pair<int, int>> v; if( count2nd(v, 0)  == 0 &&  count2nd(v, 1)  == v.size()) /*  適当  */ マクロは  C++er  として認められないし グローバル変数は未初期化とか上書きの問題がある
lambda (関数内関数風) ヘルパ関数として切り出したいけど状態渡すのが面倒 int count2nd(const std::vector<std::pair<int, int>> &v, int k) { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; } std::vector<std::pair<int, int>> v; if( count2nd(v, 0)  == 0 &&  count2nd(v, 1)  == v.size()) /*  適当  */ std::vector<std::pair<int, int>> v; auto count2nd = [&] (int k) -> int { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; }; if( count2nd(0)  == 0 &&  count2nd(1)  == v.size()) /*  適当  */ 関数内関数的にその場でヘルパ関数を定義できる
lambda (関数内関数風) ヘルパ関数として切り出したいけど状態渡すのが面倒 int count2nd(const std::vector<std::pair<int, int>> &v, int k) { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; } std::vector<std::pair<int, int>> v; If( count2nd(v, 0)  == 0 &&  count2nd(v, 1)  == v.size()) /*  適当  */ std::vector<std::pair<int, int>> v; auto count2nd = [&] (int k) { return count_if(v.begin(), v.end(), [&] (const std::pair<int, int>&p) { return p.second == k; }); }; if( count2nd(0)  == 0 &&  count2nd(1)  == v.size()) /*  適当  */ 関数内関数的にその場でヘルパ関数を定義できる ※ 内部でも  lambda  を使った場合
lambda (関数内関数風) 応用編 再帰関数を定義したい ->  std::function  と組み合わせ std::vector<std::vector<int>> adj; int goal; std::vector<bool> visited(adj.size()); // auto dfs = …  だとエラーが出る std::function<bool(int)> dfs =  [&](int n) -> bool { if(n == goal) return true; visited[n] = true; for(int i: adj[n]) { if(!visited[i]) { bool res =  dfs (i); if(res) return true; } } return false; };  ※ std::funciton  は関数オブジェクトも持てる関数ポインタ的イメージ
tuple いちいち構造体を宣言し、かつ比較演算子を 用意するのがかったるい struct data { int level, value, cost; }; bool operator<(const data& d1, const data &d2){ return d1.level < d2.level || d1.level == d2.level && d1.value < d2.value || d1.level == d2.level && d1.value == d2.value && d1.cost < d2.cost; } std::set<data> s; typedef std::tuple<int, int, int> data; //  辞書式比較演算子定義済み std::set<data> s; //  こういう用意をしておくてアクセスが分かりやすいかも enum { LEVEL, VALUE, COST }; std::get<LEVEL>(*s.begin());  ref. http://topcoder.g.hatena.ne.jp/cafelier/20110816/1313498443 tuple  にお任せ
initializer_list vector  とかの内容を自由に初期化する方法がない std::vector<int> v(5); int init[5] = { 1, 3, 2, 5, 4 }; std::copy(init, init + 5, v.begin()); std::vector<int> v({1, 3, 2, 5, 4}); //  コンストラクタ呼び出しは  ()  じゃなくて  {}  でもできるようになったので //  以下も  OK std::vector<int> v{1, 3, 2, 5, 4}; -> 初期化リスト (initializer_list) をとるコンストラクタを定義できる map  だって初期化できる std::map<std::string, int> v = { { &quot;first&quot;, 1 }, { &quot;second&quot;, 2 } };
initializer_list 応用編 min / max int minimum =  std::min(std::min(a, b), std::min(c, d)); int minimum =  std::min({a, b, c, d}); min / max  は  2  引数だったので複数値の  min / max  をとりたい場合は多段で適用する必要があった -> initializer_list  を受けられるようになったので一発で  OK
initializer_list 応用編 固定値のリストでループ int init[] = { 1, 2, 3, 5, 8, 13, 21 }; for(std::size_t i = 0; i < sizeof(init) / sizeof(init[0]); ++i) { std::cout << init[i] << std::endl; } for(int i : { 1, 2, 3, 5, 8, 13, 21} ) { std::cout << i << std::endl; } 固定値を持つ配列を用意して添え字でループ -> Range-based for  と  initializer_list  の組み合わせで こんな簡潔に
unordered_map / unordered_set map / set  はあるけど計算量が  O(log n) std::set<int> s; s.insert(5); //  挿入  O(log n) s.erase(5); //  削除  O(log n) s.count(3); //  検索  O(log n) std::unordered_set<int> s; s.insert(5); //  挿入  O(1) s.erase(5); //  削除  O(1) s.count(3); //  検索  O(1) //  トレードオフはメモリ使用量 -> いわゆるハッシュテーブルとして unordered_map / unordered_set  が導入 ※ hash_map / hash_set  だと既存実装とぶつかるので別名で導入
新規アルゴリズム(iota) インデックス用配列の初期化めんどい std::vector<int> vdata; std::vector<int> index(vdata.size()); for(std::size_t i = 0; i < index.size(); ++i) index[i] = i; std::sort(index.begin(), index.end(), [&](int n1, int n2) { return vdata[n1] < vdata[n2]; }); for(int i: index) { std::cout << i << &quot; : &quot; << vdata[i] << std::endl; } std::vector<int> vdata; std::vector<int> index(vdata.size()); std::iota(index.begin(), index.end(), 0); // 0  が初期値で  0, 1, 2, … std::sort(index.begin(), index.end(), [&](int n1, int n2) { return vdata[n1] < vdata[n2]; }); for(int i: index) { std::cout << i << &quot; : &quot; << vdata[i] << std::endl; } -> 地味に仕事をする  std::iota #define RNG(c) (c).begin(), (c).end()  でさらに便利
新規アルゴリズム(*_of) -> all_of / any_of / none_of 名前通り std::vector<int> v; if(std:: all_of (v.begin(), v.end(), [](int n){ return n >= 5; })) { /*  全要素  5  以上の場合  */ } if(std:: any_of (v.begin(), v.end(), [](int n){ return n >= 5; })) { /*  どれか 1 要素でも  5  以上の場合  */ } if(std:: none_of (v.begin(), v.end(), [](int n){ return n >= 5; })) { /* 5  以上の要素がない場合  */ }
ちょっとした改善 (map::at) map::operator[]  のせいで  const  性の維持が面倒 int get(const std::map<std::string, int> &m, const std::string &s) { //  map::operator[]  に  const  版はないのでこう書けない //  return m[s]; return  m.find(s)->second ; } int get(const std::map<std::string, int> &m, const std::string &s) { return  m.at(s) ; } -> const  版のある  at()  で  OK
対応微妙 (regex, emplace) -> regex( 正規表現 ) で文字列処理が容易に! ※でも  gcc(libstdc++)  は対応微妙 std::vector<pair<std::string, int>> v; v. push_back(std::make_pair(std::string(&quot;hoge&quot;), 5)) ; -> emplace  で  insert  時等の生成を省略 ※でも  gcc(libstdc++)  はコンテナ毎に対応微妙 std::vector<pair<std::string, int>> v; v. emplace_back(&quot;hoge&quot;, 5) ; ※ 直接、挿入する場所に値を生成するイメージ
まとめ 今なら  Codeforces  でも  C++0x  使用者は 少ないので 「同一言語使用者内で世界 10 位以内 ( ???」 とか言える C++0x  は大変便利なので 競技プログラミングでもばりばり使うべき
参考文献 競技プログラマのための C++0xB http://topcoder.g.hatena.ne.jp/cafelier/20110816/1313498443 Wikipedia C++0x http://ja.wikipedia.org/wiki/C%2B%2B0x cpprefjp https://sites.google.com/site/cpprefjp/
ご清聴ありがとうございました。

More Related Content

C++0x in programming competition

  • 1. 競技プログラミングからみた C++0x OSC 名古屋 2011 8/20 (土) はじめての競技プログラミング 蛇足 @yak_ex / 新 康孝 (CSNagoya)
  • 2. 自己紹介 氏名: 新 康孝 ( あたらし やすたか ) Twitter ID: yak_ex Web: http://yak3.myhome.cx:8080/junks C++ / Perl が主戦場 現在、仕事でコードに触れていないので 競技プログラミング( TopCoder 、 Codeforces )で 潤い補充 闇の軍団に憧れるただの C++ 好き
  • 3. C++0x とは 国際標準化機構 ISO で規格化されている C++ 規格 (ISO 14882) の次期版仮称 200x 年代に発行予定だったが間に合わなかったため 0x は 16 進プレフィクスということに http://www2.research.att.com/~bs/C++0xFAQ.html 先頃 Final Draft が承認され規格発行を待つ状態であり正式名称 14882:2011 となる予定 小修正だった 14882:2003 とは異なり、言語自体また標準ライブラリに非常に大きな変更が加えられている 本稿では競技プログラミングから見た C++0x の利点について紹介する Codeforces で gcc 4.6 での C++0x 機能が使えるのでそれを基準として説明する
  • 4. 目次 auto Range-based for Lambda tuple initializer_list unordered_map / unordered_set 新規アルゴリズム (iota, all_of) ちょっとした改善 (map::at) 対応微妙 (regex / emplace)
  • 5. auto 例えば set の要素についてループする場合、 Iterator の型を書くのが面倒 set<int> s; for( set<int>::iterator it = s.begin(); it != s.end(); ++it) { if(*it == 5) it = s.erase(it); } set<int> s; for( auto it = s.begin(); it != s.end(); ++it) { if(*it == 5) it = s.erase(it); } -> auto にしておけばコンパイラが型を導出してくれる
  • 6. Range-based for 例えば単純に set の要素についてループする場合、 C++03 では色々と面倒 Range-based for ループですっきり コンテナ s の各要素を v に割り当ててループ std::set<int> s; for( std::set<int>::iterator it = s.begin(); it != s.end(); ++it ) std::cout << *it << std::endl; std::set<int> s; for( auto it = s.begin(); it != s.end(); ++it ) std::cout << *it << std::endl; auto でもやっぱり微妙 set<int> s; for( auto v : s ) std::cout << v << std::endl;
  • 7. lambda (無名関数オブジェクト) <algorithm> は便利だけど関数オブジェクト書くのが面倒でループで書いてしまう // 関数オブジェクト struct check2nd { check2nd(const int &n) : n(n) {} bool operator()(const std::pair<int, int> &p) { return p.second == n; } const int &n; }; std::vector<std::pair<int, int>> v; int k; return std::count_if(v.begin(), v.end(), check2nd(k)); // 自前ループ std::vector<std::pair<int, int>> v; int k; int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result;
  • 8. lambda (無名関数オブジェクト) <algorithm> は便利だけど関数オブジェクト書くのが面倒でループで書いてしまう ↓ その場で関数オブジェクトが書ける スコープ内の変数を参照可能 // lambda vector<std::pair<int, int>> v; int k ; return std::count_if(v.begin(), v.end(), [&](const std::pair<int, int>& p) { return p.second == k; } ); 中身が return だけなら戻り値の型を省略可能 [&](const std::pair<int, int>& p) -> bool { return p.second == k; } [&] はスコープ内変数の参照の仕方の指定
  • 9. lambda (関数内関数風) ヘルパ関数として切り出したいけど状態渡すのが面倒 int count2nd(const std::vector<std::pair<int, int>> &v, int k) { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; } std::vector<std::pair<int, int>> v; if( count2nd(v, 0) == 0 && count2nd(v, 1) == v.size()) /* 適当 */ マクロは C++er として認められないし グローバル変数は未初期化とか上書きの問題がある
  • 10. lambda (関数内関数風) ヘルパ関数として切り出したいけど状態渡すのが面倒 int count2nd(const std::vector<std::pair<int, int>> &v, int k) { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; } std::vector<std::pair<int, int>> v; if( count2nd(v, 0) == 0 && count2nd(v, 1) == v.size()) /* 適当 */ std::vector<std::pair<int, int>> v; auto count2nd = [&] (int k) -> int { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; }; if( count2nd(0) == 0 && count2nd(1) == v.size()) /* 適当 */ 関数内関数的にその場でヘルパ関数を定義できる
  • 11. lambda (関数内関数風) ヘルパ関数として切り出したいけど状態渡すのが面倒 int count2nd(const std::vector<std::pair<int, int>> &v, int k) { int result = 0; for(int i=0;i<v.size(); ++i) if(v[i].second == k) ++result; return result; } std::vector<std::pair<int, int>> v; If( count2nd(v, 0) == 0 && count2nd(v, 1) == v.size()) /* 適当 */ std::vector<std::pair<int, int>> v; auto count2nd = [&] (int k) { return count_if(v.begin(), v.end(), [&] (const std::pair<int, int>&p) { return p.second == k; }); }; if( count2nd(0) == 0 && count2nd(1) == v.size()) /* 適当 */ 関数内関数的にその場でヘルパ関数を定義できる ※ 内部でも lambda を使った場合
  • 12. lambda (関数内関数風) 応用編 再帰関数を定義したい -> std::function と組み合わせ std::vector<std::vector<int>> adj; int goal; std::vector<bool> visited(adj.size()); // auto dfs = … だとエラーが出る std::function<bool(int)> dfs = [&](int n) -> bool { if(n == goal) return true; visited[n] = true; for(int i: adj[n]) { if(!visited[i]) { bool res = dfs (i); if(res) return true; } } return false; }; ※ std::funciton は関数オブジェクトも持てる関数ポインタ的イメージ
  • 13. tuple いちいち構造体を宣言し、かつ比較演算子を 用意するのがかったるい struct data { int level, value, cost; }; bool operator<(const data& d1, const data &d2){ return d1.level < d2.level || d1.level == d2.level && d1.value < d2.value || d1.level == d2.level && d1.value == d2.value && d1.cost < d2.cost; } std::set<data> s; typedef std::tuple<int, int, int> data; // 辞書式比較演算子定義済み std::set<data> s; // こういう用意をしておくてアクセスが分かりやすいかも enum { LEVEL, VALUE, COST }; std::get<LEVEL>(*s.begin()); ref. http://topcoder.g.hatena.ne.jp/cafelier/20110816/1313498443 tuple にお任せ
  • 14. initializer_list vector とかの内容を自由に初期化する方法がない std::vector<int> v(5); int init[5] = { 1, 3, 2, 5, 4 }; std::copy(init, init + 5, v.begin()); std::vector<int> v({1, 3, 2, 5, 4}); // コンストラクタ呼び出しは () じゃなくて {} でもできるようになったので // 以下も OK std::vector<int> v{1, 3, 2, 5, 4}; -> 初期化リスト (initializer_list) をとるコンストラクタを定義できる map だって初期化できる std::map<std::string, int> v = { { &quot;first&quot;, 1 }, { &quot;second&quot;, 2 } };
  • 15. initializer_list 応用編 min / max int minimum = std::min(std::min(a, b), std::min(c, d)); int minimum = std::min({a, b, c, d}); min / max は 2 引数だったので複数値の min / max をとりたい場合は多段で適用する必要があった -> initializer_list を受けられるようになったので一発で OK
  • 16. initializer_list 応用編 固定値のリストでループ int init[] = { 1, 2, 3, 5, 8, 13, 21 }; for(std::size_t i = 0; i < sizeof(init) / sizeof(init[0]); ++i) { std::cout << init[i] << std::endl; } for(int i : { 1, 2, 3, 5, 8, 13, 21} ) { std::cout << i << std::endl; } 固定値を持つ配列を用意して添え字でループ -> Range-based for と initializer_list の組み合わせで こんな簡潔に
  • 17. unordered_map / unordered_set map / set はあるけど計算量が O(log n) std::set<int> s; s.insert(5); // 挿入 O(log n) s.erase(5); // 削除 O(log n) s.count(3); // 検索 O(log n) std::unordered_set<int> s; s.insert(5); // 挿入 O(1) s.erase(5); // 削除 O(1) s.count(3); // 検索 O(1) // トレードオフはメモリ使用量 -> いわゆるハッシュテーブルとして unordered_map / unordered_set が導入 ※ hash_map / hash_set だと既存実装とぶつかるので別名で導入
  • 18. 新規アルゴリズム(iota) インデックス用配列の初期化めんどい std::vector<int> vdata; std::vector<int> index(vdata.size()); for(std::size_t i = 0; i < index.size(); ++i) index[i] = i; std::sort(index.begin(), index.end(), [&](int n1, int n2) { return vdata[n1] < vdata[n2]; }); for(int i: index) { std::cout << i << &quot; : &quot; << vdata[i] << std::endl; } std::vector<int> vdata; std::vector<int> index(vdata.size()); std::iota(index.begin(), index.end(), 0); // 0 が初期値で 0, 1, 2, … std::sort(index.begin(), index.end(), [&](int n1, int n2) { return vdata[n1] < vdata[n2]; }); for(int i: index) { std::cout << i << &quot; : &quot; << vdata[i] << std::endl; } -> 地味に仕事をする std::iota #define RNG(c) (c).begin(), (c).end() でさらに便利
  • 19. 新規アルゴリズム(*_of) -> all_of / any_of / none_of 名前通り std::vector<int> v; if(std:: all_of (v.begin(), v.end(), [](int n){ return n >= 5; })) { /* 全要素 5 以上の場合 */ } if(std:: any_of (v.begin(), v.end(), [](int n){ return n >= 5; })) { /* どれか 1 要素でも 5 以上の場合 */ } if(std:: none_of (v.begin(), v.end(), [](int n){ return n >= 5; })) { /* 5 以上の要素がない場合 */ }
  • 20. ちょっとした改善 (map::at) map::operator[] のせいで const 性の維持が面倒 int get(const std::map<std::string, int> &m, const std::string &s) { // map::operator[] に const 版はないのでこう書けない // return m[s]; return m.find(s)->second ; } int get(const std::map<std::string, int> &m, const std::string &s) { return m.at(s) ; } -> const 版のある at() で OK
  • 21. 対応微妙 (regex, emplace) -> regex( 正規表現 ) で文字列処理が容易に! ※でも gcc(libstdc++) は対応微妙 std::vector<pair<std::string, int>> v; v. push_back(std::make_pair(std::string(&quot;hoge&quot;), 5)) ; -> emplace で insert 時等の生成を省略 ※でも gcc(libstdc++) はコンテナ毎に対応微妙 std::vector<pair<std::string, int>> v; v. emplace_back(&quot;hoge&quot;, 5) ; ※ 直接、挿入する場所に値を生成するイメージ
  • 22. まとめ 今なら Codeforces でも C++0x 使用者は 少ないので 「同一言語使用者内で世界 10 位以内 ( ???」 とか言える C++0x は大変便利なので 競技プログラミングでもばりばり使うべき
  • 23. 参考文献 競技プログラマのための C++0xB http://topcoder.g.hatena.ne.jp/cafelier/20110816/1313498443 Wikipedia C++0x http://ja.wikipedia.org/wiki/C%2B%2B0x cpprefjp https://sites.google.com/site/cpprefjp/