24
競技プログラミングからみた C+ +0x OSC 名古屋 2011 8/20 (土) はじめての競技プログラミング 蛇足 @yak_ex / 新 康孝 (CSNagoya)

C++0x in programming competition

  • Upload
    yak1ex

  • View
    1.525

  • Download
    3

Embed Size (px)

DESCRIPTION

Presented at Open Source Conference 2011 Nagoya on 2011/8/20.

Citation preview

Page 1: C++0x in programming competition

競技プログラミングからみた C++0x

OSC名古屋 2011 8/20(土) はじめての競技プログラミング 蛇足

@yak_ex / 新 康孝 (CSNagoya)

Page 2: C++0x in programming competition

自己紹介

氏名: 新 康孝 (あたらし やすたか ) Twitter ID: yak_ex Web: http://yak3.myhome.cx:8080/junks

C++ / Perl が主戦場 現在、仕事でコードに触れていないので競技プログラミング( TopCoder、 Codeforces)で潤い補充

闇の軍団に憧れるただの C++ 好き

Page 3: C++0x in programming competition

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 機能が使えるのでそれを基準として説明する

Page 4: C++0x in programming competition

目次

auto Range-based for Lambda tuple initializer_list unordered_map / unordered_set 新規アルゴリズム (iota, all_of) ちょっとした改善 (map::at) 対応微妙 (regex / emplace)

Page 5: C++0x in programming competition

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 にしておけばコンパイラが型を導出してくれる

Page 6: C++0x in programming competition

Range-based for ループですっきり

コンテナ s の各要素を v に割り当ててループ

Range-based for 例えば単純に set の要素についてループする場合、 C++03では色々と面倒

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;

Page 7: C++0x in programming competition

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;

Page 8: C++0x in programming competition

lambda (無名関数オブジェクト) <algorithm>は便利だけど関数オブジェクト書くのが面倒でループで書いてしまう

↓ その場で関数オブジェクトが書ける

スコープ内の変数を参照可能// lambdavector<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; }

[&] はスコープ内変数の参照の仕方の指定

Page 9: C++0x in programming competition

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 として認められないしグローバル変数は未初期化とか上書きの問題がある

Page 10: C++0x in programming competition

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()) /* 適当 */

関数内関数的にその場でヘルパ関数を定義できる

Page 11: C++0x in programming competition

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 を使った場合

Page 12: C++0x in programming competition

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 は関数オブジェクトも持てる関数ポインタ的イメージ

Page 13: C++0x in programming competition

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 にお任せ

Page 14: C++0x in programming competition

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});// コンストラクタ呼び出しは () じゃなくて {} でもできるようになったので// 以下も OKstd::vector<int> v{1, 3, 2, 5, 4};

→初期化リスト (initializer_list)をとるコンストラクタを定義できる

map だって初期化できるstd::map<std::string, int> v = { { "first", 1 }, { "second", 2 }};

Page 15: C++0x in programming competition

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

Page 16: C++0x in programming competition

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 の組み合わせでこんな簡潔に

Page 17: C++0x in programming competition

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 だと既存実装とぶつかるので別名で導入

Page 18: C++0x in programming competition

新規アルゴリズム (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 << " : " << 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 << " : " << vdata[i] << std::endl; }

→ 地味に仕事をする std::iota

#define RNG(c) (c).begin(), (c).end() でさらに便利

Page 19: C++0x in programming competition

新規アルゴリズム (* _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 以上の要素がない場合 */}

Page 20: C++0x in programming competition

ちょっとした改善 (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

Page 21: C++0x in programming competition

対応微妙 (regex, emplace)

→regex(正規表現 )で文字列処理が容易に!

※でも gcc(libstdc++) は対応微妙

std::vector<pair<std::string, int>> v;v.push_back(std::make_pair(std::string("hoge"), 5));

→emplace で insert 時等の生成を省略 ※でも gcc(libstdc++) はコンテナ毎に対応

微妙

std::vector<pair<std::string, int>> v;v.emplace_back("hoge", 5);

※直接、挿入する場所に値を生成するイメージ

Page 22: C++0x in programming competition

まとめ

今なら Codeforces でも C++0x 使用者は少ないので「同一言語使用者内で世界 10位以内 ( キリッ」とか言える

C++0x は大変便利なので競技プログラミングでもばりばり使うべき

Page 23: C++0x in programming competition

参考文献

競技プログラマのための C++0xBhttp://topcoder.g.hatena.ne.jp/cafelier/20110816/1313498443

Wikipedia C++0xhttp://ja.wikipedia.org/wiki/C%2B%2B0x

cpprefjphttps://sites.google.com/site/cpprefjp/

Page 24: C++0x in programming competition

ご清聴ありがとうございました。