2014年4月15日火曜日

Google Code Jam 2014 Qualification Round

今年も GCJ に参加しているわけですが、無事、Qualification Round は突破した模様です。一応全完。今回も C++11 で書こう、は有効の状態です。あまり活用してないですが。まず最初にテンプレ。

// gcc version 4.8.2 with -std=c++11
 
#include <iostream>
#include <sstream>
#include <iomanip>
 
#include <iterator>
 
#include <algorithm>
#include <numeric>
#include <utility>
#include <limits>
 
#include <string>
 
#include <vector>
#include <deque>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>
 
#include <tuple>
#include <initializer_list>
 
#include <cmath>
 
// Boost library can be retrieved from http://www.boost.org/
// 1.52 is used
 
#pragma GCC diagnostic ignored "-Wconversion"
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#pragma GCC diagnostic warning "-Wconversion"
 
typedef unsigned long long ULL;
typedef long long LL;
typedef unsigned long UL;
typedef unsigned int UI;
typedef unsigned short US;
typedef unsigned char UC;
 
#define RNG(v) (v).begin(), (v).end()
#define REP(v, e) for(UI v = 0U; v < e; ++v)
#define REP_(v, s, e) for(UI v = s; v < e; ++v)
#define REPV(v, e) for(v = 0; v < e; ++v)
#define REPV_(v, s, e) for(v = s; v < e; ++v)
 
using namespace std;
 
template<class Integer>
inline auto
IR(Integer first, Integer  last) -> decltype(boost::irange(first, last))
{ return boost::irange(first, last); }
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        cout << "Case #" << casenum+1 << ": " << endl;
    }
 
    return 0;
}

Range-based for loop 用に IR() を作ってます。元々 REP マクロ(というかマクロ全般)があまり好きではないので基本 Range-based for loop 使用ですが元々のテンプレで使っていた部分から外すほど修正もしていない感じです。では、以下各問題について。

Problem A. Magic Trick

やるだけ。わざわざ set 2 つ作って set 演算してますが、1 つだけ作っといてもう一方は読み込みながら判定、の方が無駄が少ないですね。後、サイズ既知の vector の場合は for(auto &val : v) { cin >> v; } でさくっと読み込めますけどこれくらい簡単に set とかも読み込みたい感じ。inserter みたいな感じでラッパ作ればいけそうではありますが。

set<int> read()
{
    set<int> t;
    int ans; cin >> ans;
    for(int i : IR(1,5)) {
        int n;
        if(i != ans) {
            for(int j: IR(0,4)) { cin >> n; }
        } else {
            for(int j: IR(0,4)) { cin >> n; t.insert(n); }
        }
    }
    return t;
}
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        set<int> s1 = read();
        set<int> s2 = read();
        set<int> sr;
        set_intersection(RNG(s1), RNG(s2), inserter(sr, sr.end()));
        if(sr.size() > 1) {
            cout << "Case #" << casenum+1 << ": " << "Bad magician!" << endl;
        } else if(sr.size() == 0) {
            cout << "Case #" << casenum+1 << ": " << "Volunteer cheated!" << endl;
        } else {
            cout << "Case #" << casenum+1 << ": " << *sr.begin() << endl;
        }
    }
 
    return 0;
}

Problem B. Cookie Clicker Alpha

ループで回した場合のループ回数を確認するのが面倒だったのでへたれて不等式を解いて一撃にしてしまいました。$$ X/{2+(n+1)F} ≤ C/{2+(n+1)F} + X/{2+(n+2)F} $$ n と n+1 で立式すればよかったんですが、なぜか n+1 と n+2 で立ててしまったので微妙にずれた感じです。一応、累積誤差を考えて数が小さい方から加算するようにしていますが気にしなくても良かったかも。

int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin << cases;
    REP(casenum, cases) {
        long double C, F, X; cin << C << F << X;
        int thr = floor((F*X - 2*C - F*C)/(F*C));
        long double result = 0;
        if(thr <= 0) {
            result += X / (2+(thr+1)*F);
            for(int i : IR(0, thr+1)) {
                result += C / (2 + (thr-i)*F);
            }
        } else {
            result = X / 2;
        }
        cout << "Case #" << casenum+1 << ": " << fixed << setprecision(7) << result << endl;
    }
 
    return 0;
}

Problem C. Minesweeper Master

Cookie Clicker には Orteil does not endorse and has no involvement with Google Code Jam. と書いてあるのに Minesweeper には Microsoft Windows への言及があるのに同様の文言がなかったのは何か意味があるのでしょうか。

DP で解いた人も居るようですが、場合わけでやりました。書いてる時には(コピペ分も多いので)そんなに長く書いたつもりはなかったのですが、結果としては結構長いですね。

全体を貫く考え方としては、(1行、あるいは1列の場合でなければ)空きマスが最低2行あるいは2列繋がっていないと広がらない、です。また、c は必ず左上にしています(一般性を失わない)。最初の場合分けとしては、1行の場合、1列の場合、2行の場合、2列の場合、それ以外(3行3列以上)の場合で分けています。行・列を転置してしまえば場合わけは少なくなりますが、転置するのも面倒なんでコピペベースでいいや、という駄目な手抜きです。

1行あるいは1列の場合は、impossible は無しで右端あるいは下端から詰めるだけ。

2行あるいは2列の場合は爆弾が奇数の場合は基本 impossible ですが、1 マスだけ残っている場合は OK。OK の場合は右端あるいは下端から詰めるだけです。

最後、3行3列以上の場合は、さらに場合わけしています。最低2行あるいは2列必要ということから考えると、1) M ≦ (R-2)*(C-2) の場合は、左端および上端2列分を開けておいて右下から詰めていけば良い 2) R*C-M < 9 の場合、R*C-M = 2, 3, 5, 7 の場合 impossible、それ以外の場合は左上の状態は R*C-M の値に合わせて決め打ち、で良いことが分かります。最後、R*C-M ≧ 9 の場合ですが、空きマス8個分は確定、(2,2) の位置で偶奇を調整して、後は右2列を下から1行(2マス)づつ、上2行を右から1列(2マス)づつ埋めていけば良いことになります。

Short input については全入力パターンが (1+2+3+4+5)^2 = 225 通り、制約の数字から全パターンが入力になってそうってことでローカルで全入力作っといて目視で確認してから subumit してます。時間に余裕のある Qualification Round ならではって感じですね。

void c1(int R, int M)
{
    cout << "c" << endl;
    for(int i: IR(0, R - M - 1)) cout << "." << endl;
    for(int i: IR(0, M)) cout << "*" << endl;
}
 
void r1(int C, int M)
{
    cout << "c";
    for(int i: IR(0, C - M - 1)) cout << ".";
    for(int i: IR(0, M)) cout << "*";
    cout << endl;
}
 
void c2(int R, int M)
{
    if(2*R-M == 1) {
        cout << "c*\n";
        for(int i: IR(0, R - 1)) cout << "**" << endl;
    } else if(M % 2 || 2*R-M == 2) {
        cout << "Impossible" << endl;
    } else {
        cout << "c.\n";
        for(int i: IR(0, R - M/2 - 1)) cout << ".." << endl;
        for(int i: IR(0, M/2)) cout << "**" << endl;
    }
}
 
void r2(int C, int M)
{
    if(2*C-M == 1) {
        cout << "c";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
        cout << "*";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
    } else if(M % 2 || 2*C-M == 2) {
        cout << "Impossible" << endl;
    } else {
        cout << "c";
        for(int i: IR(0, C - M/2 - 1)) cout << ".";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
        cout << ".";
        for(int i: IR(0, C - M/2 - 1)) cout << ".";
        for(int i: IR(0, M/2)) cout << "*";
        cout << endl;
    }
}
 
char result[50][50];
void gen(int R, int C, int M)
{
    for(int i: IR(0,R)) { for(int j: IR(0,C)) { result[i][j] = '*'; } }
    if(R*C - M == 1) { // nothing to do
    } else if(R*C - M == 4) {
        result[0][0] = result[0][1] = result[1][0] = result[1][1] = '.';
    } else if(R*C - M == 6) {
        result[0][0] = result[0][1] = result[1][0] = result[1][1] = result[2][0] = result[2][1] = '.';
    } else if(R*C - M == 8) {
        for(int i: IR(0,3)) { for(int j: IR(0,3)) { result[i][j] = '.'; } }
        result[2][2] = '*';
    } else if(R*C - M < 9) {
        cout << "Impossible" << endl;
        return;
    } else if(M <= (R-2)*(C-2)) {
        for(int i: IR(0,2)) { for(int j: IR(0,C)) { result[i][j] = '.'; } }
        for(int i: IR(0,R)) { for(int j: IR(0,2)) { result[i][j] = '.'; } }
        int MM = (R-2)*(C-2);
        for(int i: IR(2, R)) {
            if(M == MM) break;
            for(int j: IR(2,C)) {
                result[i][j] = '.'; --MM;
                if(M == MM) break;
            }
        }
    } else {
        for(int i: IR(0,3)) { for(int j: IR(0,3)) { result[i][j] = '.'; } }
        int MM = R*C - 9;
        if(((R*C-M) % 2) == 0) {
            result[2][2] = '*'; ++MM;
        }
        for(int i: IR(3, R)) {
            if(M == MM) break;
            for(int j: IR(0, 2)) {
                result[i][j] = '.';
            }
            MM-=2;
        }
        for(int i: IR(3, C)) {
            if(M == MM) break;
            for(int j: IR(0, 2)) {
                result[j][i] = '.';
            }
            MM-=2;
        }
    }
    result[0][0] = 'c';
    for(int i: IR(0,R)) { for(int j: IR(0,C)) { cout << result[i][j]; } cout << endl; }
}
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        int R, C, M; cin >> R >> C >> M;
        cout << "Case #" << casenum+1 << ":" << endl;
        if(R == 1) { r1(C, M); }
        else if(C == 1) { c1(R, M); }
        else if(R == 2) { r2(C, M); }
        else if(C == 2) { c2(R, M); }
        else { gen(R, C, M); }
    }
 
    return 0;
}

Problem D. Deceitful War

War について Ken の戦略は Naomi の戦略に依らないということなので順序は気にしなくとも良いはず。ということで、Ken としては勝つときには最小の余力(Naomi の値以上で最小のものを出す)で勝ち、負けるときは最小で出すという自然な戦略で良いはずです。では、Deceitful War ではどうなのかといことで、とりあえず最初にできるだけ強いカードを捨てさせて(相手の最大に自分の最小をぶつける)、後は先後逆転で計算する形で書いたら結果が合いません(この時点でなんで先後逆転で計算しようと思ったのか細かいことを忘れちゃってます)。入力例の最後をソートして眺めると、勝ちようがない最小の1枚だけ捨てて後は Naomi が全て勝ってるんですよね。ってことで捨てる枚数でループさせて最大値を取る、で一応結果は合うのですが……。ここで天啓。「あれ?相手のカードも戦略も全部分かってるんだから相手の出すカード全部操作できるんじゃね?」これで先後逆転で一発計算すればいいや、となりました。実際には submit 時点では宣言した値と勝敗が異なってはいけないという条件を忘れていたので考えが足りなかったのですが、Ken の最大値より大きな値を言い続ければ最小から出てくるのでそこで勝ち続けて、勝てなくなるところからは最小値より小さな値を言い続ければやっぱり小さい方から出てきてそこで負ければよい、となるので問題ないことになります(仮に本当にこのまま実際にに人間相手にやったら普通ばれるでしょうけれども)。

int calc_war(const set<double> &naomi, set<double> ken)
{
    int ret = 0;
    for(double i : naomi) {
        auto it = ken.upper_bound(i);
        if(it != ken.end()) {
            ken.erase(it);
        } else {
            ken.erase(ken.begin());
            ++ret;
        }
    }
    return ret;
}
 
int main(void)
{
    ios_base::sync_with_stdio(false);
 
    UI cases; cin >> cases;
    REP(casenum, cases) {
        set<double> naomi, ken;
        int N; cin >> N;
        for(int i: IR(0, N)) { double t; cin >> t; naomi.insert(t); }
        for(int i: IR(0, N)) { double t; cin >> t; ken.insert(t); }
        size_t result_war = calc_war(naomi, ken);
        size_t result_dwar = naomi.size() - calc_war(ken, naomi);;
        cout << "Case #" << casenum+1 << ": " << result_dwar << ' ' << result_war << endl;
    }
 
    return 0;
}

まとめ

TopCoder Open より Google Code Jam の方が相性がいい感じなのですが、なかでも Qualification Round は時間を気にせずに考えたり書けるんで一番楽しいです。本戦考えるとあんまり良くない参加方法ではありますが。今年もどこまでいけるか分かりませんが、Round1 は突破したいなぁ。

2014年4月8日火曜日

EclipseCoder C++ プラグインの拡張

競技プログラミング界の雄 TopCoder では競技環境でプラグインの使用が可能ですが、それを利用して Eclipse 上でコーディングできるようにしてしまおうというのが EclipseCoder です。実際便利ではあるのですが、C++ でやっている場合に特に面倒なのが以下 2 点です。

  1. 例えば、Windows 上で VC++ が入っている状態だけど Cygwin GCC を使いたい場合などに、毎回ツールチェインの設定が必要だったりする(環境依存)。
  2. C++11 有効にしたりオプション変えるのも毎回設定が必要。

ということで、これらの設定が可能なように C++ プラグインを修正しました。GitHub のリリースページから net.fornwall.eclipsecoder.ccsupport_0.2.5y1.zip をダウンロード、中の net.fornwall.eclipsecoder.ccsupport_0.2.5.jar で Eclipse インストール先フォルダの同名ファイルを上書きしてください。Window -> Preferences -> EclipseCoder -> C++ の設定下側に、コンボボックスが 2 つ増えています。上がツールチェインの設定、下が設定のコピー元プロジェクトです。

設定コピー元プロジェクトについては EclipseCoder で作成されたプロジェクトを元にした方が無難なので、まずはツールチェインについては自分の使用するものを選び(選択必須)、プロジェクトについては空白にしておきます。この状態でいつものように TopCoder に接続、Practice にでも入って問題を開くと新規プロジェクトが作成されます。この時、特に必須ではありませんが作成されたプロジェクトを設定コピー元プロジェクトだと分かるようリネームしておいてもいいかもしれません。このプロジェクトで EclipseCoder でのプロジェクト作成時に適用しておきたい設定を適当に変更します。例えば (Cygwin GCC で) C++11 有効にする場合だと↓の感じになります。

これで再度 Window -> Preferences -> EclipseCoder -> C++ を開いて、今度は下側のコンボボックスで先ほど設定を変更したプロジェクトを選びます。上側コンボボックスで選択しているツールチェインと整合していなかったりすると OK 時にメッセージが表示されて適用できません。SRM 615 Div2 250 で作成したプロジェクトを設定コピー元プロジェクトにした場合、↓のようになります。

後は、普通に問題を開いていくと先ほどの設定コピー元プロジェクトの設定内容が反映されているはずです。注意点として、設定を取得する際にはプロジェクトがオープンされている必要があるため、設定コピー元プロジェクトは必要があればオープンされます。そしてプラグイン側ではクローズしません。毎回オープン・クローズ繰り返すのもどうかな、と思ったのでこういう仕様になっています。

よろしければ使ってみてください。とりあえずしばらくして問題がなさそうであれば upstream に pullreq を出すつもりです。実のところ 1. については pullreq がすでに upstream にマージ済みでリリースバージョンまで更新されているのですが、にも関わらずサイトでリリースされていません。作者さんが忘れているだけなんじゃないかな、という気もしますが、pullreq 出したときに「Java 初心者なんでチェックしてね」みたいなことを書いてしまったので確認するのもなんだかなと思っているうちに 1 年が経ってしまいました。

なお、本プラグインを使用したせいで Eclipse が落ちて rating 下がった等、いかなる結果についても責任を負いかねますのでその辺は承知の上でお願いします。【追記】もし使用されてる方がいらっしゃいましたら「問題なく動いてるよ」でも良いのでコメントを付けて頂けると嬉しいです。pullreq の支えになりますので。

2013年12月17日火曜日

main() のすり替え in C++

[2013/12/18] ptr_umain 経由の呼び出しで引数を付けていなかった点を修正。

ある種のライブラリの場合、main() の呼び出し前に処理をしておきたい場合があります。ものすごく真っ当な方法で言うならスタートアップルーチンを書くべきところではありますが、その場合、ライブラリを使う側でもオプション指定が必要になったりしてちょっと面倒になります。こういう時に、ライブラリ側で main() を定義してしまいユーザーコード側では

// main.h
#define main _umain
#ifdef __cplusplus
extern "C" int _umain(int argc, char **argv);
#else
extern int _umain(); // No specification for parameters in C
#endif

// main.c
#include "main.h"
int main(int argc, char **argv) { /* ... */ } // replaced with _umain

のようにヘッダを読み込ませることによって main() をすり替えてしまい、ライブラリ側の main() からすり替えた _umain() を呼び出す、といったことが行われる場合があります。例えば SDL (http://www.libsdl.org/) なんかはこれを使っているようです。

が、問題が一つ。main() は引数が固定されていません。実用的には、以下の3種類くらいあると考えてよいでしょう。

int main(void);
int main(int argc, char **argv);
int main(int argc, char **argv, char **envp);

C の場合、これでもそんなに問題になりません。すり替え用マクロが有効であるとして、上記コードのように extern int _umain(); としておけば、C 言語では引数に関しては指定がない扱いであり、かつ可変引数関数を実現する C の呼び出し規約上、上記いずれの定義であっても _umain(argc, argc, envp); と呼び出してしまえば、普通の処理系ならまず問題なく動作するはずです。

が、C++ の場合、プロトタイプ宣言が必須であり、また、関数の型チェックが必ず行われます。つまり main() がどの形式で定義されていたかによって呼び分ける必要が出てくるわけです。前述の SDL では main() の型を int main(int, char**) に限定することで回避しているようです。最初から特定ライブラリを使う前提であればそれでもいいのですが、今作成中のライブラリ(UTF-8 Win32 API)は後付けの形での利用を想定しているので限定はちょっとつらいです。

要はユーザーがどの形式で main() を定義したか判別する方法があればいいわけです。そこで(処理系依存ですが) GCC の weak symbol と alias を使用してみました。

int _umain_stub()
{
 return 0;
}

// for C
extern "C" int _umain(...) __attribute__((weak, alias("_Z11_umain_stubv")));
// for C++
extern int _umain() __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc) __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc, char ** argv) __attribute__((weak, alias("_Z11_umain_stubv")));
extern int _umain(int argc, char ** argv, char **envp) __attribute__((weak, alias("_Z11_umain_stubv")));

static int(*ptr_umain)(...)                   = static_cast<int(*)(...)>(_umain);
static int(*ptr_umain0)()                     = static_cast<int(*)()>(_umain);
static int(*ptr_umain1)(int)                  = static_cast<int(*)(int)>(_umain);
static int(*ptr_umain2)(int, char**)          = static_cast<int(*)(int, char**)>(_umain);
static int(*ptr_umain3)(int, char**, char**)  = static_cast<int(*)(int, char**, char**)>(_umain);

template<typename T>
static inline bool is_target(T t)
{
 return t != reinterpret_cast<T>(_umain_stub);
}

int main(int argc, char **argv, char **envp)
{
 if(is_target(ptr_umain))  return ptr_umain(argc, argv, envp);
 if(is_target(ptr_umain0)) return ptr_umain0();
 if(is_target(ptr_umain1)) return ptr_umain1(argc);
 if(is_target(ptr_umain2)) return ptr_umain2(argc, argv);
 if(is_target(ptr_umain3)) return ptr_umain3(argc, argv, envp);
 return -1; // Not reached if main() is user-defined
}

通常、同名の関数定義がある場合、多重定義エラーになります。weak symbol を使うと、他に同名の定義がない場合だけ有効になる定義を作ることができます。デフォルト実装の定義を作っておいて、場合によってはより高速な実装に差し替えることも可能、みたいな使い方が典型的なユースケースです。alias はある関数を別の関数の別名として定義できる機能です。上記コードでは C/C++ の _umain() 系を全て _umain_stub(void) の別名かつ weak symbol としています。ユーザーがある形式の main() を定義した場合、マクロで _umain() にすり替えられていずれかの weak symbol が上書きされます。結果として _umain_stub(void) と値(アドレス)が変わるため、それを is_target() で判別して呼び分けているわけです。

で、話が済んでいれば幸せだったのですが、なぜか StrawberryPerl 5.18.1.1 32bit 中の GCC を使って、↑ main() のあるソースファイルで #include <iostream> や #include <cstdio> すると関数ポインタの値がずれます。Cygwin の i686-w64-ming32-g++ だと問題ありません。両方とも GCC-4.7.3 なんですが。例えば実際のアドレスが、
__Z7_umain_v (ユーザー定義の _umain(void)) -> 0x401560
__Z11_umain_stubv (_umain_stub(void)) -> 0x40195e
である場合に、バイナリ中で参照されるアドレスがそれぞれ、0x40117e, 0x40157c だったりします。両方とも 0x3e2 だけずれています。バグなのか設定がおかしいのかちょっと分かりませんがとりあえず以下のようなコードで無理やり補正すると一応動作はします。ユーザー定義の _umain() と _umain_stub(void) の 2 種類があるだけで、かつ、ユーザー定義は 1 つだけ存在するはず、という前提で多数派が実際には _umain_stub(void) であるとみなしてその差を補正しています。

template<typename T>
static inline unsigned long get_offset(T t)
{
 return reinterpret_cast<unsigned long>(_umain_stub) - reinterpret_cast<unsigned long>(t);
}

template<typename T>
static inline void add_offset(T& t, unsigned long offset)
{
 t = reinterpret_cast<T>(reinterpret_cast<unsigned long>(t) + offset);
}

void fix_fptr()
{
 std::map<unsigned long, int> counter;
 ++counter[get_offset(ptr_umain)];
 ++counter[get_offset(ptr_umain0)];
 ++counter[get_offset(ptr_umain1)];
 ++counter[get_offset(ptr_umain2)];
 ++counter[get_offset(ptr_umain3)];
// assert(counter.size() == 2);
 unsigned long offset = counter.begin()->second > (++(counter.begin()))->second ? counter.begin()->first : (++(counter.begin()))->first;
 add_offset(ptr_umain, offset);
 add_offset(ptr_umain0, offset);
 add_offset(ptr_umain1, offset);
 add_offset(ptr_umain2, offset);
 add_offset(ptr_umain3, offset);
}

ちょっとどうだかなという感じではあるので、とりあえず、#include <iostream> や #include <cstdio> を外して様子見な感じですが謎な状態です。

2013年11月22日金曜日

\L と \U と Perl

Perl スクリプトに文字列を渡すのに

count -t '\t'

のような感じでエスケープを使いたいけれど、Perl コアでそのものの機能は提供されていないということで String-Unescape というモジュールを作成中です。もちろん eval 使えば済むのは分かってるんですけど、文字列 eval はなるたけ避けたいということで。その中でぶつかった(自分的には)驚きの Perl 仕様について書いてみます。

ダブルクウォート内で \Q と \E で囲った範囲の英数アンダーバー以外をエスケープできる(quotemeta がかかる)というのは Perl 使いではそれなりに知られた事実だと思います。

say "\Q[|]\E";
# \[\|\+\]

同様に範囲で効く変換として \L, \U も存在は知っているという方も多いでしょう(最近は \F と言うのもあります)。で、これらは重ねることができると perldoc perlop には書いてあります。

\L, \U, \F, and \Q can stack, in which case you need one \E for each. For example:
 say"This \Qquoting \ubusiness \Uhere isn't quite\E done yet,\E is it?";
# This quoting\ Business\ HERE\ ISN\'T\ QUITE\ done\ yet\, is it?

さて、では問題です。以下はどのように出力されるでしょうか。

say "\Q[A\U[a\L[A\E[a\E[A\E";

正解は \[A\[A\[a\[a[A です。この時点で「は?」って感じですが、続いて第 2 問。

say "\U[a\Q[a\L[A\E[a\E[a\E";

正解は [A\[A[a[a[a です。もう訳がわからないという感じですが、真っ当に追っかける気を無くすコードを感覚で読み取ったところ、\U 中の \L や \L 中の \U があると、その前の \U, \L まで(途中に \Q とかがあればそれも)無効になった後で、新しく \L, \U が有効になるようです。\L や \U が一回しか有効にならないように \E が適宜補われると考えると分かりやすいかもしれません。

say "\Q[A\U[a\L[A\E[a\E[A\E";
say "\Q[A\U[a\E\L[A\E[a\E[A\E"; # 直前の \U が無効
# \[A\[A\[a\[a[A
# \[A\[A\[a\[a[A
say "\U[a\Q[a\L[A\E[a\E[a\E";
say "\U[a\Q[a\E\E\L[A\E[a\E[a\E"; # \U\Q が無効
# [A\[A[a[a[a
# [A\[A[a[a[a

\L...\U...\E...\E ってやっても外側の \L しか効かないから無駄じゃねーか、という意図であろうというのは推測できますが、個人的には「いや、書かれたとおりに実行しろよ」と言いたくなる挙動です。

もう一つ、エスケープ関連でお節介じゃないの?という機能として、\L\u と \U\l をそれぞれ \u\L と \l\U に置き換えるというものがあります。

say "\L\uaAA\E \U\lAaa\E";
# Aaa aAA

\u してから \L したら結局 \L だけじゃねーかってのは分かりますが、「勝手に書き換えるな、警告出せ」と思うわけです。

ちょっと気を利かせたつもりで全体として訳が分からなくなる、というのはある意味とても Perl らしいという気もしますが。元々 String-Unescape は Perl の処理と一致させることを目指そうと思っていたわけですが、この 2 つの挙動(特に前者)は無理して再現する必要はないだろうということで実装しないつもりでいます。

2013年9月26日木曜日

YAPC::Asia Tokyo 2013 参加メモ(二日目)

前稿に引き続き、聴講したトークの雑感を箇条書き的に。

Mojoliciousでつくる!Webアプリ入門 by Yusuke Wada

  • WAF 自作も楽しいけど、そこは本題じゃないんだからとりあえず WAF 使っとくのが良い、と。
  • ライブコーディングでさくっと作られてたのですごい簡単感が伝わってたと思います。
  • 「分けることで分かる」「少しづつやる(ことで俺スゲー感を持続)」俺スゲー感はモチベーション持続に有用ですよ、本当。新しいことやると楽しいんですよね。
  • CCF は WAF 使ってないんですが、特に model がきちっと分かれてないところは次の大改修前には手直ししたいという気になりました。

Programming AWS with Perl by Yasuhiro Horiuchi

  • with Perl 要素少なめ。
  • Python 製 AWS CLI が提供されてるので、Perl での自動処理用にはそれに対する wrapper AWS::CLIWrapper を使えば良いのではないかと、という感じですかね。
  • CLI と API 直叩きモジュールとどちらが良いという質問がありましたが、自動処理用には CLI 経由でいいでしょうけど、アプリ的にはモジュール使用なんじゃないですかね。

What's new in Carton & cpanm by Tatsuhiko Miyagawa

  • もちろん cpanm は使ってますけど Carton は使ってないんですよね。個人の趣味の領域だとそこまで必要性を感じないというか。
  • git URL 可になったのは patch 版指定とかできて嬉しい感じではあるんですけど、Dist::Zilla で github に置くと普通はインストール可にならないんで、その辺どうするのがいいのかな、と思ったりします。

GitHubでつくる、たのしい開発現場 by Hiroki OHTSUKA

  • どっちかっていうと精神よりというか、本来そういうことの方が重要だとは思うんですが、やっぱり具体的なツールとか Tips の方が分かりやすいんですよね。

可視化のためのPerl by Muddy Dixon

  • 可視化には Explanatory (ストーリー説明) と Exploratory (ストーリー探索) の 2 種類がある。
  • マイニング技術者はドメイン知識が少ない v.s. ドメイン専門家はマイニングの知識が少ないので可視化してドメイン専門家に見てもらおう、と。
  • DataCube の開発止まってるじゃんということで、Data::Cube をリリースされたとのことですが、「僕はこの手の処理はRでやります」という衝撃の結末からは、どうせこのモジュールも開発止まるんでしょ?、感が激しくします。

本当にあったレガシーな話 by Daisuke Maki

  • livedoor blog のモダン化について。モダン Perl 入門 増補改訂版 に書かれるそうです。題名は増補改訂版になっていますが 80% 変わっているとのこと。
  • 発表的にはとりあえず mod_perl dis だったと思っておけば大体合ってるんじゃないでしょうか。
  • とにかくログをとることがまず最初。設定切り替えには use constant による定数たたみ込み最適化の利用をすると良い。
  • 他色々あったんですが、$0 変えると ps で見たときに分かるというのは Tips は面白かったです。

スマフォアプリ開発を支える認証認可アーキテクチャ by Rieko Suzuki

  • 執筆記事の著者写真の時点でなんかもう色々と違う気がしましたが「糞アプリ」発言で逆に安心しました。

PhantomJSによる多岐にわたる広告枠の確実な表示テスト by Yusuke Yanbe

  • Selenium::Remote::Driver を使えば headless ブラウザである PhantomJS を操作できるのでそれを使ってテストするという話。

フルテストも50msで終わらせたい 〜 FreakOutの取り組み 〜

  • 最初にさすがに 50ms は無理ですとの潔いお言葉。
  • 一日目の soh335 さんの発表と結構被っていたとのこと。テスト用ユーティリティモジュールも用意されているそうです。
  • 実行順序に依存するテストを避けるために、テスト順序のシャッフルがおすすめとのこと。
  • File::Find, List::MoreUtils, Parallel::ForkManager, Net::OpenSSH, TAP::Harness といった CPAN モジュールを組み合わせて分散実行することで 2,000 秒 over → 180 秒に短縮。
  • CPAN モジュールの組み合わせというのがとても Perl らしくていいですね。
  • 「高速なテストは開発文化を変える」手元でやるより push した方が速いのでとりあえず push する文化に→コードが早期共有されてレビューも進み品質も向上。

LT

とりあえず分かる範囲でメモ。名前が分からない方とかもし抜けてたらすみません。

  • テストカバレッジについて http://coveralls.io/ というサービスがある。
  • DBIx::* の新しいモジュール。
  • makamaka さんの同人活動レポート。
  • HTTP から SMTP 送信ができる Haineko について。
  • Perl 入学式と近所の Perl Mongers のおかげで初心者でも「つぶやいてないでコード書け」というサービスができました!という話。いい LT だったと思います。
  • papix さんの Perl 入学式活動レポート。
  • kamipo さんの mysql-build の話。
  • もっとネタモジュールを!ということで netacpan.org できました。
  • kamadango さんのマイクロコミュニケーションの話。
  • tokuhirom さんの Test::Power っていうか HTTP::Body::Builder 作ったった。
  • mruby_nginx_module について。
  • pjf さんの method add_datapoint( Str: $goal!, Int:$timestamp) みたいに書ける Method::Signatures の紹介。
  • takesako さんの Perl クイズ。近藤嘉雪先生が優勝してました。チート級。識別子が 251 文字以下とかそんな制限あったんですね……

Keynote by Tomohiro Ikebe

  • 「Management and Perl culture」というタイトル。
  • マネージャーで画像検索するとリア充 マネージャーって管理するというより支える方じゃないの?
  • できるエンジニアにできるからマネージャーやれ、は最悪。とはいえチーム内のリーダーシップは必要。
  • 社内に対する広報(エンジニアの仕事が意味のある仕事だってことを伝える)ことも大事。
  • ……そんなに年が変わらないということにちょっとショックを受けました。

YAPC::Asia Tokyo 2013 クロージング by Daisuke Maki

  • 牧さん、櫛井さんのツートップによる YAPC::Asia はこれで終了、とのこと。後を継ぐ人が居ないとこれで終わり、ということなんでしょうか?
  • のべ参加者 1,000 人オーバー。うーむ、すごい。

まとめと余談

  • 今年の YAPC は Perl の話題が多いな、とかいう tweet を見ましたが、確かに Perl 寄りな感じで個人的には嬉しかったです。
  • 本当は今年発表できたらなーと朧気に思っていたのですが(Dist::Zilla の話とか)、Minilla と Milla のトークが応募されてるし、しかも Ricardo SIGNES ご本人来てるしってんで無理ゲーでしたね。LT できたらなーというネタは今年は余裕で間に合わなかったので来年に LT できたらなーと思っています。
  • 今回新しいノート PC (VAIO Pro 13)で参加しました。今まで一台のノート PC をどのシーンでも使い回す感じだったので 2kg 前半まで選択肢に入れていたのですが、重量というよりもバッテリーの持ちからこの手のイベントではちょっときついな、ということでモバイル用として購入。シートバッテリがなかったとしてもイベント中はもってそうでした。重量はあんまり気にしてなかったんですが実際に使ってみると、部屋の移動のときに横に抱えるのも楽だし AC アダプタもなくてそのまま運べるしで良かったと思います。

YAPC::Asia Tokyo 2013 参加メモ(一日目)

遅ればせながら、YAPC::Asia Tokyo 2013 参加メモ。

目が覚めたらいきなり当初乗るつもりだった電車の時間でしたが、最初の説明にわずかに遅刻する程度の時間で着。いきなり席が埋まってる感じでちょっとびびりました。メインの藤原洋記念ホール 1階席は後ろからの入り口が2ヶ所あるのですが、一度どっちかの入り口から入ってしまうと途中で別の通路側まで行くのは狭い席の間を移動するしかなく(ステージ直前では移動できたかも)手前通路側が埋まりがちになっていた気がします。 以下、聴講したトークの雑感を箇条書き的に。

Postcards from the Edge: The State of Perl 5 Development by Ricardo SIGNE

  • Dist::Zilla でも大変お世話になっている RJBS こと Ricardo SIGNES 氏の Perl5 の今後についての発表。
  • とりあえず pumpkin というのは Perl 5 のリリースマネージャ(というのが正確か分かりませんが)的な立ち位置の人だ、というのは以前の参加の時に聞いているので知っているのですが、何言ってるのかさっぱり分からない参加者の人とか居たんじゃないかな、というのは少し気になりました。
  • 「Perl5 は Perl5 であり続ける(意訳)」という言葉は印象的でした。
  • postfix dereferencing @{$hoge}$hoge->@* と書けるというのは確かにちょっとキモいですが Perl らしいキモさな気がします。実際 $hoge->[3]->{key} とか書いた後、あぁ array dereference しなきゃってんで、カーソル戻して @{ } で囲うとかいうのは超頻出パターンなので便利にはなるはず。
  • 本体開発側では互換性によってはじかれるパッチとか改良とかは少ないかもしれないですが、利用者側で互換性のために使用しない機能、とかは割とある気がします。モジュール書いててもより低いバージョンをサポートするためにより面倒な方法に書き直したりすることもありますし。強制アップデート的なことができれば楽かもしれませんが。

Perlのモジュールを公開するときに気をつけておいた方がよい**個のこと by Kenichi Ishigaki

  • 基本的に CPANTS の Kwalitee の説明という感じで、Test::Kwalitee::Extra とか書いてるくらいなので大体のところは既知でした。
  • バージョンの形式(v0.1.5 だとか 0.1.5 だとか 0.001005 だとか)について聞いてみましたが、「特殊なことしないのであれば 0.01 とか使うのが楽」とのことでした。なんかコンセンサス的なものが欲しいです……

Perl and Riak - 分散データストア Riak を Perl から "爆速" で使うために - by Tatsuro Hisamori

  • FreakOut さん所属。(前からそうで気付いてなかっただけかもしれないですが) YAPC での存在感が大きくなってる気がします。
  • 内容についてはどうこう言えるほど知らない分野なので割愛。
  • 「継続的に運用していくシステムにおいては"テストができる"ことはマスト要件」これ本当大事。

Inside amon2-livedoor-setup.pl with web application development 2013 by Kazuhiro Osawa

  • Team Geek 買いました。
  • 溺愛の初期設定スクリプトだと社内事情が考慮されてないので、各社でひな形設定スクリプトを作っておくとよいよ、という話。
  • コピペを排除するためのひな形スクリプトがコピペ対象に、というのはものすごく有りそうな話。
  • 「開発から本運用に必要なすべてを盛り込む」これは確かに大事だけど抜けやすそうな気がします。

SPDY、HTTP/2.0の使い方 by takesako

  • 使いどころ、というところまでは達してなくて今使うならどうするか、くらいでしょうか。
  • HTTPS 接続を強制する HSTS (HTTP Strict Transport Secruity) は初めて聞きました。
  • Outbound Port 80 Blocking は笑ってしまいましたが、でもそれでもいい気がしますね。企業等のフィルタリングが辛いかもしれませんが。MITM 的に中継する HTTPS フィルタ proxy とかは SSL によるセキュリティの根本的なコンセプトを損なう最低の存在だと思っているので許容不可。

Types and Perl Language by Masahiro Honma

  • 人全然居ないんじゃないの?と思ったら座れませんでした。
  • トラブルがあってデモができなかったり、本論に入る前のところで終わったりした感じで、もっとちゃんと聞きたかったですね。
  • ひょっとしたらトラブルが無かったらそうだったのかもしれませんがバックグラウンドの説明を廃して Typed Perl の実例側から話す、という流れもありだったんじゃないかと思います。

モダンPerlリファクタリング by Naoya Ito

  • Perl 徹底攻略 掲載ネタです。毎回全く新しいネタを話すというのはとても難しいことだとは思うのですが、既に書籍を持っている状態で聞くと割と悲しい気分になったりするので、発表→書籍出版の順番になるようにしてもらえると宣伝にもなって双方がハッピーかもしれません。いや、トーク内容にも書いてあるし確認しておけば良かったんですけど。
  • とにかくまずはテストを作ること、細かいテストをちまちま書いていくという意味ではなく、とにかく大外の I/F 切ってテストを書いて、あとはテストとコード修正を並行していく、という感じでしょうか。
  • Test::Base で DATA セクションに置くとかまではやらないですけど、テストケースをデータにまとめておくってのは良くやりますね。

perl な web application のためのテスト情報 by soh335

  • テストを書いてもらうために、テスト用の共通モジュール t/Util.pm を作ってテストを書く障壁を下げている、とのこと。
  • Web application に限らないですし、各種 Test モジュールの簡単な紹介もあるのでさらっとスライド見ておくといいんじゃないでしょうか。
  • コードだけじゃなく、ファイル命名規則だとか用語チェックだとかもテストすればよい、というのは確かにその通りでわざわざ別の枠組み使わなくても TAP で合わせてしまえばいいですね。

はてなのサーバ管理ツールの話 by Yuuki Tsubouchi

  • ものすごくざっくりまとめると、運用系のツールは各種あって組み合わせの柔軟度は持っていたいけど設定が分散するので中央のコントローラーは内製といった感じでしょうか。
  • 知見や経験がほとんどないところなんでなんとも言えないですが一通り紹介というよりはポイントに絞って話してもらったほうが良かったかもしれません。

LT

題目と発表者リストがあるかと思ったらスケジュールの所にないですね……。とりあえず分かる範囲でメモ。名前が分からない方すみません。

  • kazuho さんの prove の発表。prove は汎用 taskrunner である、と。(今後使うか分からない)拡張子限定の解除とか Tips 満載でした。
  • mobile factory の人。えーと何でしたっけ?モテる台詞でしたっけ?今年のネタ枠。
  • Tachikoma の話。依存ライブラリ指定を更新して commit して pullreq 出してくれる。n-click から 1-click だと商売になって、1-click から 0-click だと革命という台詞が印象に残りました。
  • yappo さんの HTTP::Builder::Body が欲しい、という話。
  • hirose31 さんの inspect running perl process という話。strace とかだと syscall しか見られないし gdb 系だと特定の環境に依存するしということで、inspect-perl-proc を作成。例えば perl プロセスが詰まってるときにどこの関数で詰まってるかとかが分かる。いや、これすごいんじゃないでしょうか。
  • yusukebe さんの YAPC::NA 行ってきた話。どこから来たの?で話ができるし、こちらから話を切り出せば聞いてくれるよ、とのこと。
  • eikichi さんの YAPC::Tohoku やりたい、という話
  • comewalk さんの OSS に対する貢献の話。別にコード書くだけが貢献じゃない、と。規模によるでしょうが、自分のような弱小個人だったりすると単に反応があるだけでも嬉しいですしバグレポだけでも「使ってくれてる人居たんだ(泣)」という気分になります。
  • bayashi さんの module 紹介。アップロードしたところで自分もすぐに使わなくなるかもしれないですが、ローカルにおいてるだけだと絶対に今の自分にしか使えないですが、上げれば誰かが使えるかもしれないですし、テストやドキュメントを書くので将来の自分にとっても役に立ったりするかもしれません。Milla, Minilla, Dist::Zilla とか使うと簡単にモジュールをアップロードできます。おすすめ。
  • gfx さんの Power Assert と Emscripten による perl.js の話。
  • mizuki_r さんの p5-Spica (Web service wrapper) の話。
  • Songmu さんの Riji (日記) の話。いや、中国語分からないっす。

以下次稿

2013年5月4日土曜日

Google Code Jam 2013 Round 1A

遅ればせながら。

結果

ooo-o- 46pts の rank 546 ということで無事 Round1 通過です。どうも TCO より GCJ の方が相性が良い気がします。とりあえず PATH の設定がおかしくて exe 実行時に DLL がないって怒られたり、ブラウザのデフォルトダウンロードパスが gcj2012 になってたりスタートの所で結構いらいらしてましたが通って良かったです。Problem B を short 限定解で割り切ったのが正解だった気がします。テンプレートは前回参照です。なお、Problem Analysis 未読状態です。

Problem A. Bullseye

基本二分探索でやるだけ、ですが単純にやると 64 ビットでオーバーフローするようになってるのが味噌でしょうね。式的には n 個目の円を描くのに必要なインクの量は (r+2n-1)2 - (r+2n-2)2 = 2r+4n-3 なので c 個目の円まで描くのに必要なインクの量は Σ(2r+4n-3) = 2cr+2c(c+1)-3c = c(2r+2c-1) となります。t ≧ c(2r+2c-1) を満たす最大の c を求めるということで二分探索すればいいわけですが 64 ビットだとオーバーフローしてしまうので、t/c ≧ 2r+2c-1 で計算しています。2r+2c-1 が整数になるので t/c が整数除算(切り捨て)でも > だった場合が = になるだけなので問題有りません。

多倍長をサポートしている言語を使う、GCJ は外部ライブラリの使用も可能なので多倍長ライブラリを使うといった対処もあったのですが面倒だったのでオーソドックスに先に割り算しておく、で対処しました。が、gcc は 4.6 から __int128 をサポートしているようなのでそれを使うのが一番簡単だったかもしれません。

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  ULL r, t;
  cin >> r >> t;

  ULL l_ = 1ULL, r_ = 2000000000ULL;
  REP(i, 50) {
   ULL tt = (l_+r_)/2;
   if(t/tt >= 2*r+2*tt-1) {
    l_ = tt;
   } else {
    r_ = tt;
   }
  }

  cout << "Case #" << casenum+1 << ": " << l_ << endl;
 }

 return 0;
}

Problem B. Manage your Energy

しばらく考えていましたが Large 解は見えなさそうだったので Short 限定解として DP で解きました。大抵 DP の時は memoize 的に書く場合が多いのですが、超正統派な DP なのでループで書いてます。今回 Large 解を出せていませんが、一旦 Short 解を作って通しておいて実績を作り、Large 解を作った時の検証に使う、というのはそれなりに有効な戦略だと思うので 1 つの解で Short/Large 両方というのにはそんなに拘わらなくていいと思っています。

終了後のコメント見ると greedy と言っている人がいました。時間中に考えていたことと合わせると、全 Energy は E+(N-1)R で、最大係数に最長時間を割り当てて、それより後では後ろの係数が大きいところは Energy を消費せずに積む、最大係数より前では前の係数が大きいところでより消費させてその場では Energy を消費させない、みたいにずらしていくことでいける、かも?

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  UI E, R, N; cin >> E >> R >> N;
  vector<UI> v(N); for(auto &i : v) { cin >> i; }
  vector<vector<UI>> t(N, vector<UI>(E+1, 0));
  REP(i, t[N-1].size()) {
   t[N-1][i] = i * v[N-1];
  }
  REP(j_, N-1) {
   UI j = N-2-j_;
   REP(i, t[j].size()) {
    UI temp = 0;
    REP(k, i+1) {
     UI kk = i-k+R;
     if(kk > E) kk = E;
     temp = max(temp, k * v[j] + t[j+1][kk]);
    }
    t[j][i] = temp;
   }
  }
  cout << "Case #" << casenum+1 << ": " << t[0][E] << endl;
 }

 return 0;
}

Problem C. Good Luck

いやー、さすが GCJ。出題者側も果敢に挑戦してくるなーと思ったのがこれ。100%の解ではなく一定率以上の正解であれば良い、という問題。実は Small が 1 つだけど複数回可能で、Large が 1 回のみ、というのに特化した Makefile を書いてたりするので今回割と悲しかったりするわけですが、問題読んだ後で「やってくれる」という感じでテンション上がりました。この調子だと近似解を求める問題とかも早晩出てきそうな感じがします。

Small 1 の方は数字が 2, 3, 4, 5 の 4 種類しかないので 3 か 5 が選ばれた場合には確定、2 と 4 の取り扱いをどうするかです。全ての選択パターンから分布をとって判定、とか可能な元の数字の組み合わせから該当するパターンの生起確率をとる、とかが正道な気がしますがベストな解を求める必要はないので手抜きです。素因数分解して 3 と 5 が確定する部分はまず確定。なお情報が全くないところについてはどの数字でも関係ないのでとりあえず 2 で埋める方針です。26, 25 がある場合は 4 が 2 つあることが確定。24 がある時は 2,2,4 とかも有り得ますけど 4,4 の方が確率が高い(はずな)のでこれも 4 が 2 つ。23 がある時は 2,2,2 も有り得ますけど以下同文で 4 が 1 つ。22 の時は残り枚数が 1 枚の時は 4 が 1 つで確定。後は、21 の場合があれば 4 なし。なければ 4 が 1 つです。少なくともここは最適な戦略じゃない気がします。ただ下表のように分が悪い賭でもない、と思いますが、多分。

 積が2積が4
2,2(0,1)
(1,0)
(1,1)
2,4(1,0)(0,1)

一応、これで一回で通っています。ジャッジが正答率出してくれれば良いのにという意見を見ましたが、自分でジャッジ作って試してみるというのが割と良い戦略だったかもしれないですね。

int main(void)
{
 ios_base::sync_with_stdio(false);

 UI cases; cin >> cases;
 REP(casenum, cases) {
  cout << "Case #" << casenum+1 << ":" << endl;
  UI R,N,M,K; cin >> R >> N >> M >> K;
  REP(i, R) {
   vector<UI> pr(K); for(auto &val : pr) { cin >> val; }
   int mc2 = 0, mc3 = 0, mc5 = 0;
   vector<UI> dc2(7);
   for(auto val : pr) {
    int c2 = 0, c3 = 0, c5 = 0;
    while(val % 2 == 0) { ++c2; val /= 2; }
    while(val % 3 == 0) { ++c3; val /= 3; }
    while(val % 5 == 0) { ++c5; val /= 5; }
    ++dc2[c2];
    mc2 = max(mc2, c2);
    mc3 = max(mc3, c3);
    mc5 = max(mc5, c5);
   }
   REP(j, mc3) { cout << 3; }
   REP(j, mc5) { cout << 5; }
   int p2 = 3 - mc3 - mc5;
   int c4 = 0;
   if(dc2[6]) c4 = 3;
   else if(dc2[5]) c4 = 2;
   else if(dc2[4]) c4 = 2; //
   else if(dc2[3]) c4 = 1; //
   else if(dc2[2]) {
    if(p2 == 1) c4 = 1; //
    else if(dc2[1] == 0) c4 = 1;
   }
   REP(j, c4) { cout << 4; }
   REP(j, 3-mc3-mc5-c4) { cout << 2; }
   cout << endl;
  }
 }

 return 0;
}

総評

どうにか今年も T シャツ争奪権を得られました。もらえるといいなぁ。