Page List

Search on the blog

2011年4月12日火曜日

Haskell勉強記(1)

最近Haskellを勉強しています。
これからこまめにHaskellを勉強して、学んだ内容を公開していきます。

今日の内容は、
  1. pattern match
  2. list match
  3. concatenation
です。

例題として、quick sortを書いてみます。
a = [31, 10, 19, 90, 100, 1, 3, 4]
qsort [] = []
qsort (x:xs) = qsort (filter (<x) xs) ++ [x] ++ qsort (filter (>=x) xs)
main = print $ qsort a
qsortの定義が2つありますが、これはpattern matchと呼ばれています。
C++では、引数の型や型の数によって関数の挙動を変えることができます。(overload)
Haskellの場合は、引数の値に応じて関数の戻り値を変えることができます。

次に、(x: xs)という表現に注目。
Haskellでは、リストを先頭部分とそれ以外に分解して表現することができます。
xがリストの先頭要素、xsはそれ以外です。
これも、一種のpattern matchと考えることができます。

filterに関しては、一般的な関数言語の機能なので割愛。

最後に++演算について。
これは、concatenationです。リストを連結します。

Haskellだと、quick sortがかなりシンプルに書けますね!
今日はここまで。

2011年4月11日月曜日

バグを出さないプログラムテクニック(1)

よくあるバグの代表例は、
  • 無限ループ
  • out of bounds 系エラー
あたりだろう。
これらのバグは、ちょっとした事を実践することで回避することができる。

それは、whileループの禁止である。
whileループを使うと、
  • やたらと処理が複雑になったり、
  • 条件式が偽にならず無限ループになったり、
と悪いことばかり。
大抵のwhileループは、forループで書けるので、whileは禁止してしまってもいいかと思う。
(あと余談だが、ショートコーダーはwhileループは使わないらしい。)

例えば、この問題。

for文で書くと、綺麗に書けます。
whileで書くと、多分カオスになるでしょう。そしてバグが出るはず・・。

for文で書いたプログラムはこちら。
vector<pair<int, int> >vec, ret;
int main() {
int n, s, t;

scanf("%d", &n);
while (n--) {
scanf("%d %d", &s, &t);
vec.PB(MP(s,t));
}

sort(ALL(vec));
int pos = 0;
for (;pos < (int)vec.size(); pos++) {
int s = vec[pos].first;
int t = vec[pos].second;

for (; pos+1 < (int)vec.size() && vec[pos+1].first <= t; pos++)
t = max(t, vec[pos+1].second);

ret.PB(MP(s, t));
}

REP(i, ret.size())
printf("%d %d\n", ret[i].first, ret[i].second);

return 0;
}


2011年4月10日日曜日

Being hooked on something makes you strong

I watched a TV drama entitled "aibou," which means buddy, co-worker or that kind of thing in Japanese, on this new year's day.
On the drama, a wife who had lost her son by a traffic accident, made explosive bombs and revenged on someone who killed her son.
On the drama, a detective, who solved the case, said "You think that an average house wife cannot make bombs, right? But one can do anything if s/he tries it all out."
And he continued, "She became really keen on studying about bomb. It made her proactive, outgoing and lively. Even though she stayed up late in the night -- 2 or 3 AM -- every day, she never looked sleepy. She looked cheerful every day."

あれ、何か。前置きが長くなった。
まー要約すると、最近仕事が忙しいことを言い訳にしてアルゴリズムの勉強をサボりがちだったけど、それは違うなと思いました。

本当に好きなこと、本当にやるべきことであれば、寝る時間を削ってでもやるべきだと。
そして、その方が活力的な生活が送れるはずだと信じています。

って、こんな精神論を書きたいわけじゃなかったのですが、
今日は、中国語の問題を解きました。


ちょっと文章が分かり難いですが、要は、
「mをn個以下に分割するパターンを求めよ。」
です。

分割数は、動的計画で解くのが定石。
分割数を知らない人はこのページを見よう!!

これくらいの規模ならDFSとかで解けそうですが、入力サイズが大きくなると動的計画じゃないと厳しそうです。。
int dp[16][16];

void init() {
REP(i, 16)
dp[i][0] = 0, dp[0][i] = 1;
dp[0][0] = 1;

FOR (i, 1, 16) FOR (j, 1, 16) {
if (i-j >= 0)
dp[i][j] = dp[i][j-1] + dp[i-j][j];
else
dp[i][j] = dp[i][j-1];
}
}

int main() {
int t, n, m;

init();
scanf("%d", &t);
REP(i, t) {
scanf("%d %d", &n, &m);
printf("%d\n", dp[n][m]);
}

return 0;
}

2011年4月6日水曜日

カントール集合

昨日、初めてフラクタルをプログラムで描画した。
実は、フラクタルは結構難しい。二次元の場合は正確かつ高度な実装能力が必要だ。

ただし、カントール集合は一次元なので簡単。

始点、終点を引数にして再帰関数で処理をすればOK。

カントール集合は、不可算集合の代表的な例らしい。
非加算集合とはその名の通り、『その要素に番号を割り振ることのできない集合』である。
なんじゃそりゃ!?って感じですね。
簡単に言うと、無限個の整数を使っても番号を割り振ることのできないくらい、濃度の濃い集合ということです。例えば、実数の集合も非加算集合です。

カントールさんは、この非加算集合の存在を数学的に示したそうです。
wikipedia(英語版)に、とても面白い内容がありました。

Cantor's diagonal argument(カントールの対角線論法)です。

要約すると、以下のような感じ。

0または、1のみから構成される無限長リストの集合を考える。
この無限長リストに番号をつけて、適当な要素列s1、s2、...を考える。

s1 = (0, 0, 0, 0, 0, 0, 0, ...)
s2 = (1, 1, 1, 1, 1, 1, 1, ...)
s3 = (0, 1, 0, 1, 0, 1, 0, ...)
s4 = (1, 0, 1, 0, 1, 0, 1, ...)
s5 = (1, 1, 0, 1, 0, 1, 1, ...)
s6 = (0, 0, 1, 1, 0, 1, 1, ...)
s7 = (1, 0, 0, 0, 1, 0, 0, ...)
   ...
とすると、対角成分(左上から右下にかけて)に移動しながら、通過した数字と異なる数字を選ぶ。
  s0 = (1, 0, 1, 1, 1, 0, 1, ...)

すると、このs0は、どのsiとも異なる。
ここで面白いことに、
  • s0は集合sに含まれる。(1, 0からなるリストなので)
  • 同時に、s0は集合sに含まれれない。(上記のような操作をすれば、集合に含まれないようなリストを作成できる。)
の両方が成立する。
これは矛盾なので、
この無限長リストに番号をつけて、適当な要素列s1、s2、...を考える。
という操作は不可能だということが分かる。

よって、集合sはs1、s2、s3、s4は数えることはできない。

どういう頭の構造をしてたら、こんなこと考えつくんでしょうね・・。

2011年3月31日木曜日

SRM BrushUp: FoxPlayingGame (501 div2 Middle)

SRM501。狐さんの回。

こんこん♪

平日なので参加できなかったので、今日解いてみた。。
が、ダメでした。

250は、sequenceが与えられるので、これにある整数nを加ると、arithmetic progression または geometric progressionになるときのnの個数を求めよ。
という感じ。(やばい、日本語へたい。。笑)

500はかなりトリッキー。DPで解いたけど、マイナスの場合ダメじゃんと気付いて、じゃあ
  • 整数の大きさ
  • 整数の絶対値
2つでDPじゃん。って思ったけどこれもダメ。。
こういうときは、例題をよく読むといい。
なるほど。。先に足すか、先に掛けるかをやればよさそう。書く。サブミット。テスト。落ちる・・

で、(paramA, paramB)が
  • {正 or 負}^2 の4分岐
  • paramBが{1000以上、未満}の2分岐
の8分岐くらいのソースを書く。通った。。でもこんなややこしいのか。。。
後でシンプルに書けることに気付く。
class FoxPlayingGame {
public:
double theMax(int nA, int nB, int paramA, int paramB) {
double a = paramA / 1000.0;
double b = paramB / 1000.0;

if (!nA) return 0;
if (!nB) return nA*a;

double ret = nA*a;
ret = max(ret, nA*a*pow(b, nB));
ret = max(ret, nA*a*pow(b, nB-1));
ret = max(ret, nA*a*b);

return ret;
}
};

この問題は、新しい思考パターンを教えてくれます。
分岐がいくつあるのかを考えるのではなくて、最終的に最大値を取りそうな値はどんなパターンがあるか最終段のみ考えればよいです。
入力があって、マッピング先だけに目を向けるという発想。。。
なるほど。良い準備をさせてもらいました。

もうちょっと分析すると、
paramA/1000.0は全部足した方がいい。(足せば足すほど、絶対値は大きくなる)
paramB/1000.0は以下の4つの選択肢がある
  • 掛けない(掛けると小さくなる)
  • 全部掛ける
  • (全部-1)掛ける(符号の都合で)
  • 1回だけかける(掛けると絶対値は小さくなるが、符号を逆転できる)
まあでも、This is what they call "hindsight is 20/20."

2011年3月26日土曜日

SRM BrushUp: probabilityToLose (500 div2 Middle)

正答率5%以下の難問だったらしい。
私も本番では解けなかった。。(ちょっと言い訳をすると、飲みに行った後のSRMだったので・・。笑)

今日、落ち着いて解いてみたら普通に解けました。
でも反省すべきところはあります。
本番は、問題の意味を追うのに必死になって問題文を何回も読んでいました。
これは明らかに時間の無駄。
問題文がよく分からないときは、サンプルを読んで、紙と鉛筆でシミュレーションすればどうすればいいのか分かります。
これは、大事なSRM攻略法の一つな気がしますね。。

この問題のポイントは、
  • 決定権のある人が投票し終わった時点で、最大票を集めた人の投票数が1の場合は無限ループ
  • 決定権のある人が投票し終わった時点で、最大票を集めた人が1人しかいない場合はその人が絶対選ばれる
  • それ以外の場合は投票対象が縮小される。⇒縮小はモジューロ演算を使えばよい
です。下手に難しく考えすぎるとhogeってしまうので、div2の500なんでそんな難しくはないだろうっていう気持ちで臨むことも大事だと思いました。

ソースはこちら。最近よく見るrngさんのコーディングスタイルにちょっと影響された(笑

class MafiaGame {
public:
double probabilityToLose(int N, vector<int> decisions) {
int M = decisions.size();
int point[N];

fill(point, point+N, 0);
REP(i, N) REP(j, M) if (decisions[j] == i) ++point[i];

int mx = *max_element(point, point+N);
if (mx == 1) return 0.0;

int cnt = 0;
REP(i, N) if (point[i] == mx) ++cnt;
if (cnt == 1) return 1.0;

double ret = 1./cnt;
while (1) {
int rm = N - mx * cnt;
if (rm % N == 0) return 0.0;
cnt = rm % cnt;
if (cnt == 1) break;
}
return ret;
}
};

2011年3月25日金曜日

Emacs 入門(4)



最近、Emacsのカスタムが面白い。
自分の好きなように設定して、自分オリジナルのエディター環境が作れるというのがいいところだと思う。

お勧めは、
compile-dwim

これを使うと、プログラムのコンパイル&実行が簡単にできる。
しかも複数の言語に対応しているという優れもの。キーバインドを設定すれば、eclipseみたいに楽にbuild and runが出来る。

あとは、画面の色とか分割とかの初期設定をいじってみた。
ついに私のemacsも、エディタからIDEへの進化を遂げましたね。