SCons入門(1)

SConsとは?

Python製のMake代替ツール。
中でPythonコードを書けるので、複雑なMake処理を簡単に書ける。
C言語の依存関係などを自動に調べる機能などがあるらしいが、使った事はないので詳しい事はわからない。


自分の場合はプログラムのビルドではなく、ゲームデータのビルドに使用した。
(プログラムのビルドならVisualCやEclipseなどの専用ツールに任せた方が楽だったりする)


C言語などのビルドでは依存関係を調べる関係上、大規模開発では速度が出ないという意見もある。
しかし、自分が使っている限りでは、ゲームデータなどのコンバートであればかなりの数のデータを処をしているが、実用十分な速度が出ていると思う。


■SCons公式
http://www.scons.org/

使い方

ビルドしたいプロジェクトディレクトリに「SConstruct」というファイルを作成する。
SConstructにビルドコマンドを記述していくが、もちろんPythonコードをそのまま書ける。
以下、簡単なSConstructの例。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
print "hello scons: %s" % os.getcwd()

コマンドライン上で「scons」と打つと、ビルドが始まる。

> scons
scons: Reading SConscript files ...
hello scons /home/xxxx
scons: done reading SConscript files.
scons: Building targets ...
scons: `.' is up to date.
scons: done building targets.

-cコマンドを付けると、クリーンアップされる。

> scons -c

独自のコマンドを実行する「Command」

自前で作ったコンバートプログラムを実行するには「Command」を使用する。
以下、自前のコンバートプログラム「my_convert.py」で、ルートディレクトリ配下のtxtファイルをコンバートする例。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import glob

#環境設定 システムパスの設定など
env = Environment(ENV = os.environ)

for input_name in glob.glob("*.txt"):
    output_name = os.path.splitext(input_name)[0]+".out"
    env.Command(
        output_name, input_name, "python my_convert.py $SOURCE $TARGET"
    )

「Command」は、出力したファイル名を返す。

env = Environment(ENV = os.environ)

print env.Command("a.out", "a.in", "python my_convert.py $SOURCE $TARGET")
# -> "a.out"

第2引数の出力ファイルをNoneにすると、出力ファイルがないタスクを作ることができる。
ファイルの更新チェックは行われずに、毎回必ず実行されるタスクとなる。

env = Environment(ENV = os.environ)

#出力ファイルがない場合、毎回実行されるタスクとなる
env.Command(None, "a.in", "python my_convert.py $SOURCE")

また、第3引数のactionにはPython関数も指定する事が可能。

def my_action(target, source, env):
    print target[0], len(target)
    print source[0], len(source)
    return None

env.Command("a.out", "a.in", my_action)

独自のビルダーを定義する「Builder」もあるが、「Command」で十分な場合が多い。

独自のビルダーを実行する「Builder」

独自のビルダーを登録する事も可能、こちらは拡張子などを自動で変換してくれる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import glob

#独自ビルダー
bld = Builder(
    action = "python my_convert.py $SOURCE $TARGET",
    suffix = '.out',
    src_suffix = '.txt',
)

#独自ビルダーを登録
env = Environment(ENV = os.environ, BUILDERS = {'MyBuilder' : bld})

for input_name in glob.glob("*.txt"):
    env.MyBuilder(input_name)

引数の解析

sconsに引数を設定する事ができる。
以下、引数「project_dir」を渡すコマンドライン

> scons project_dir=/home/prj

「arg=value」の形で書かないと、ターゲットオブジェクトの認識されてしまうので注意。
SConstructでは、「ARGUMENTS」という辞書に引数が格納される。

#引数 project_pathを取得
project_path = ARGUMENTS.get("project_path", "./")
# -> project_path = /home/prj

まとめ

自分がよく使う機能を備忘録としてのメモ。
自分の場合、ゲームデータのビルダーとして使っているので「Program」などの基本的なタスクは説明しない。しかし、Googleで「SCons」で調べれば、基本的な使い方はたくさん出てくるのでここでは割愛する。


たったこれだけ覚えるだけで、かなりの事が出来るようになる。
Pythonコードを書けるので、Pythonさえ覚えていればいちいち機能を調べる必要がないので、とても楽になる。
惜しむべきは日本語の資料が少ない事か。


SCons入門(2) - Pashango’s Blog

スキルはあるが飯は食えないかもしれない

仕事が一段落したので、久しぶりの更新です。

あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定


面白そうなのでPythonで解いてみました、実装時間は1時間くらいです。
Pythonすごいなー、C++Javaでやれって言われたら3時間では終わらんわ・・・

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
import itertools

def main(input):
    # 牌情報を"牌が何個あるか"という数値配列形式に変換
    # 例) 1223344888999 => [1, 2, 2, 2, 0, 0, 0, 3, 3]
    t = sorted([int(x) for x in input])
    num_hi = [t.count(x) for x in range(1,10)]

    # 順子・刻子・アタマのすべての要素を列挙します
    pieces = getPieces(num_hi)

    #すべての要素から4要素の組み合わせでサーチします
    ans = set()
    for p in itertools.combinations(pieces, 4):
        # 牌情報から4要素を抜き取る
        t_num_hi = num_hi[:]
        subHi(t_num_hi, p[0])
        subHi(t_num_hi, p[1])
        subHi(t_num_hi, p[2])
        subHi(t_num_hi, p[3])

        # 残り枚数2枚以上、または値がマイナスだったら無効
        if sum(t_num_hi) > 2 or min(t_num_hi) < 0:
            continue

        # 残った配牌から最後の要素を取得
        last = getLastPices(t_num_hi)
        if last:
            # 有効な要素が取得できたら、その組み合わせは有効
            # 文字列変換&ソートして集合へ追加する
            t = sorted(["".join([str(x) for x in y]) for y in p])
            t = "".join(["("+x+")" for x in t])
            t += "["+"".join([str(x) for x in last])+"]"
            ans.add(t)

    #結果表示
    for a in ans:
        print a

def getPieces(num_hi):
    # 順子・刻子・アタマのすべての要素を列挙します
    ret = []

    #刻子
    ret += [[i+1]*3 for (i,x) in enumerate(num_hi) if x>=3]
    #アタマ
    ret += [[i+1]*2 for (i,x) in enumerate(num_hi) if x>=2]
    #順子
    for i in range(10-3):
        m = min(num_hi[i:i+3])
        if m > 0:
            ret += [[i+1,i+2,i+3]]*m
    return ret

def getLastPices(num_hi):
    # 残り1〜2牌から有効な要素を取得
    sum_num = sum(num_hi)

    if sum_num == 1:
        # 残り1枚の場合は裸単騎
        return [num_hi.index(1)+1]
    elif sum_num == 2:
        if 2 in num_hi:
            # 残り2枚、かつ同種類の場合はシャンポン待ち
            return [num_hi.index(2)+1, num_hi.index(2)+1]
        else:
            for i in range(10-3):
                if sum(num_hi[i:i+3]) == 2:
                    # 残り2枚、かつ牌が数字が連続または1枚飛ばしなら
                    # 両面 or ペンチャン or カンチャン待ち
                    return [idx+1 for idx,x in enumerate(num_hi) if x==1]
    return None

def subHi(num_hi, elm):
    # 牌情報から要素を抜き取る
    for x in elm:
        num_hi[x-1] -= 1

if __name__ == "__main__":
    main(sys.argv[1])

出力結果は以下の通り。

>mahjong.py 1223344888999
(234)(234)(888)(999)[1]
(123)(44)(888)(999)[23]
(123)(234)(888)(999)[4]

>mahjong.py 1112345678999
(111)(234)(678)(999)[5]
(11)(123)(456)(999)[78]
(123)(456)(789)(99)[11]
(11)(123)(456)(789)[99]
(111)(345)(678)(999)[2]
(11)(123)(678)(999)[45]
(111)(456)(789)(99)[23]
(111)(234)(567)(999)[8]
(111)(234)(567)(99)[89]
(111)(234)(789)(99)[56]
(11)(345)(678)(999)[12]

コードは"シンプル&わかりやすさ"を重視しました。
「組み合わせ」はitertoolsを使っています、反則・・・?
きっと出題者は再帰を使って欲しいんだろうな、使いませんけど。


ほどよい難易度で問題自体はすごく楽しかったけど、これでプログラマの実力は測れるかな・・・?
麻雀ルールを知らないと準備にかなり時間かかる気がします。


でも、実際にスキルあっても飯が食えないよなー、ゲーム業界は大手(任天堂除く)でも給料安いし。
本当に人材を探したいならゲーム業界を探すといいかもしれませんよ。
安くてスキルが高い人はゴロゴロいるよー(泣)


P.S
要件定義に同牌は4枚までと明記されていない事に気づきました。
てことは「1111111111111」も待ちアリにしないとダメ・・・?
同牌を4枚以上に対応したい場合は、getPieces()を以下のように書き換えればOK

def getPieces(num_hi):
    # 順子・刻子・アタマのすべての要素を列挙します
    ret = []

    #刻子
    #ret += [[i+1]*3 for (i,x) in enumerate(num_hi) if x>=3]
    #アタマ
    #ret += [[i+1]*2 for (i,x) in enumerate(num_hi) if x>=2]
    # ↓のように書き換えます
    #刻子&アタマ
    for (i,x) in enumerate(num_hi):
        for y in range(x//3):
            ret += [[i+1]*3]
        for y in range(x//2):
            ret += [[i+1]*2]

    #順子
    for i in range(10-3):
        m = min(num_hi[i:i+3])
        if m > 0:
            ret += [[i+1,i+2,i+3]]*m
    return ret

出力結果は以下の通り。

>majong.py 1111111111111
(111)(111)(111)(111)[1]
(11)(111)(111)(111)[11]

うーん、考えすぎかな・・・?

ベイズの定理(入門編)

前回のエントリーで予告した通り、今回は「ベイズの定理」です。


事前に断っておきますが、ベイズの定理は簡単です。
内容は小学生レベルの算数ですから、解らなくても自信を持って何回か読みなおせば絶対にわかります。
(わからなかったらコメントに質問してね)

ベイズの定理ってなんぞ?

ベイズの定理とはトーマス・ベイズ(1702-1761)というイギリスの牧師によって発見されました。


今やベイズの定理はあらゆる所に使われいます、スパムメールを振り分けたり、犯罪捜査に使われたり、マーケティングに使われたり、人工知能に使われたり、沈没しちゃった潜水艦を見つけたり、株の売買に使われたり、結婚相手を見つけちゃったり・・・
ベイズ万能すぎるだろ!!


ベイズの定理」を理解はしなくても、言葉だけでも覚えていれば何かと便利です。
何かしらの問題に直面した際に、


「ふむ・・・このxにベイズの定理を使えば・・・」


とつぶやくだけで、それっぽく見えるのでオススメです。
(「バルキスの定理」くらい万能ですので、使用は自己責任でお願いします)

ベイズの前に「条件付き確率」

ベイズの定理を理解するには、みんなが大嫌いな「条件付き確率」を理解しなくてはなりません。

「条件付き確率」とは、


ある事象Bが起こる条件下で、別の事象Aが起こる確率のこと。
記号ではP(A|B)と表す。


という事です。
OK,OK、君の言いたい事はよく解ってる、よくわからないんだろ?
例えば「あなたは男だとして、イケメンである確率は?」という問題があった場合、先ほどの記号で表すと「P(イケメン|男)」となります。
P(A|B)で、Bが前提の上でAがで成り立つ確率となります。


ここで1つ有名な問題を出しましょう。

隣の家に2人の子供がいる事が解っています、ある日、隣の子供のうち1人が女の子である事が解りました。*1
隣家のお母さんに「女のお子さんはいますか?」と質問した所「はい」と答えました。
このとき、もう1人の子供も女の子である確率はいくつでしょうか?
※なお、男女比は1:1とする。


答えを記号で表すと「P(2人とも女の子|1人が女の子)」となります。
考え方としては、子供2人の組み合わせは「姉妹」、「姉弟」、「兄妹」、「兄弟」の4通りです。
「1人が女の子」という前提条件があるため、「兄弟」が除外され「姉妹」、「姉弟」、「兄妹」の3通りとなります。
その3通りの中で「姉妹」が当たる確率を求めればいいのですから、答えは「1/3」です。
「1/2」だと思った方、猛省してください。

いよいよベイズの定理

ベイズの定理」とは、まさしく今まで説明してきた「条件付き確率」を求めるための計算式なのです。
まずは「ベイズの定理」を見てましょう。


P(A|B) = P(A∧B) / P(B)


「P(B)」事前確率と呼ばれ、単純にBが起こる確率です。
(ちなみに「P(A|B)」は事後確率と呼ばれます)


「P(A∧B)」同時確率と呼ばれ、AとBの両方の事象が成り立つ確率です、∧は論理記号で論理積を表し、「(A∧B)」は「AかつB (A and B)」という意味となります。


「P(A∧B)」が難しい!と思った方、ほとんどの場合は「P(A∧B)」=「P(A)」と思ってOKです。
ごく稀に例外となる問題もありますが、そういう問題は事前情報が欠けていたりなど、どこか不自然な問題が殆どです。
だから解らない人は無理せずに、こう覚えておいてください、その内慣れてきますから :-)


ベイズの定理に関してはここで面白い問題を見つけました。20年前の早稲田大学の入試問題だそうです。

5回に1回の割合で帽子を忘れるくせのあるK君が、正月に A、B、C 3軒を順に年始回りをして家に帰ったとき、帽子を忘れてきたことに気がついた。2軒目の家 B に忘れて
きた確率を求めよ。


正直、どの家で忘れたかよりも、K君が心配ですが・・・
普通に考えれば、Bの家で忘れた確率は「4/5×1/5=4/25」(A家で忘れない確率×B家で忘れる確率)ですよね。
しかし、上記の確率では「3軒回って帽子を忘れない確率」も含まれています、「帽子を忘れている」という前提条件がついている事に注意です。
条件付き確率の記号で表すと「P(B家で忘れた確率|帽子を忘れた確率)」となり、ベイズの定理を使うと以下の式になります。


P(帽子を忘れた確率) =
  (1/5)+(4/5×1/5)+(4/5×4/5×1/5) =
    61/125


P(帽子を忘れた確率∧B家で忘れた確率) =
  P(B家で忘れた確率) =
    (4/5×1/5) = 4/25


P(B家で忘れた確率|帽子を忘れた確率) =
  P(帽子を忘れた確率∧B家で忘れた確率) / P(帽子を忘れた確率) =
    (4/25)/(61/125) = 20/61


答えは「20/61」となります、簡単でしょ?

前エントリーの問2をベイズの定理で解いてみよう

前エントリーの問2をベイズの定理で解いてみましょう。


多くの方が「扉を変える2/9、扉を変えない2/9、無効になる5/9」と答えていたのが印象的でした。
確かにこれでも間違いではありませんが、条件付き確率を理解しているとは言えません。

問2では「地震で無効にならなかった」という前提条件が付き、「P(扉を変えて当たる確率|地震で無効にならない確率)」を求めるので、以下の様になります。


P(扉を変えて当たる確率) = (4/9)×(1/2)
P(地震で無効にならない確率) = 4/9


P(扉を変えて当たる確率|地震で無効にならない確率) =
  P(扉を変えて当たる確率) / P(地震で無効にならない確率) =
    ( (4/9)×(1/2) )/(4/9) = 1/2


となり、答えは「1/2」となります。


まぁ、「変えるか、変えないか」の2択問題なのに「2/9と2/9」という答えはおかしいですよね

私が髪型で「7:3分け」にしようか「真ん中分け」にしようか悩んでいるところへ、


「2:2分けがいいよ^^」


と言ってるようなもんです。
残りの6はどこに?っていうか、ハ、ハゲちゃうわ!!


「残りの6は、近い将来なくなる可能性が高いので除いておきました^^」


く・・・くやしぃ!!

長くなったので続きます

自分でもベイズの定理で曖昧な部分があり、備忘録も兼ねて説明を書きました。
本当はベイズの定理でもっと面白い事が出来るんですが、それはまた次回に。


P.S
確率問題「トランプと3つの箱」 - kojetteの研磨日記ベイズの定理の問題が出ています、ベイズの定理の有効性を示す問題として非常によく出来ています。
時間があったら解いてみると面白いと思います。
(まだ解答が未発表です、私も解答しているけど間違ってたら非常にカッコ悪い・・・)

*1:この言い方だと、問題が確立しないので変更しました、id:kojetteさん、ありがとうございます

モンティホール問題の補講

前回のエントリーで解答をくれた方たち、ありがとうございます。


正解した方たち、おめでとうございます。
前知識なしで2問とも正解した方は、非常に頭のいい方だと思います。


残念ながら不正解だった方たち、落ち込まないでください。
私も最初にこの問題を聞いた時は、壮大に引っかかりました。


出題者は職場の女の子で、知人から聞いた問題との事でした。
一応、理数系のプログラマとしての自信もそれなりにあった私は、


「はぁ〜(深いため息)
あのねぇ、誰がそんな間違った知識を広めているかは知らないけどさ、
数学で飯を食っている僕に言わせれば、それは間違いだよ。
君もそんな嘘に騙されちゃダメだよ、よくある確率のトリックさ!」


と、女の子を優しく諫めました。
で、気になったので、あとでインターネットで調べたら・・・


うわぁぁぁぁぁぁぁぁぁぁぁぁ!!
(トラウマ発動中)


言い訳をするつもりではありませんが、モンティホール問題は一流の数学者でさえ引っかかる問題で、アメリカでも大騒動になった問題です、詳しくはid:tazumaさんのエントリーモンティ・ホール ジレンマ その3をご覧ください。

業務連絡

このエントリーを見た同僚からクレームが入りました。


「きみ、このブログの問2の問題文は、俺に出した問題文より随分とマイルドじゃないか。
俺に出題した時は、もっと難しい問題文じゃなかった?」


はい、すみません。
あのときは徹底的にあなたを貶める事が目的だったので、ちょっと難しい問題文にしました。
「これじゃ、俺がキレやすい若者みたいじゃないか」とクレームがきておりますので、彼の名誉の為に、彼へ出した問題文を掲載します。


問題:

3つの扉(A,B,C)があり、その内のどれか1つの扉に当たりが入っています。司会者はどこの扉が当たりかを知っており、あなたが選択した扉以外のハズレの扉をランダムで1つだけ開いてくれます。


あなたはAの扉を選択しました。
そこで司会者がCの扉を開けてハズレであることを示しました。
この時、あなたはBの扉に変更すると、当たる確率が2/3に上がる事は前の問題から明らかです。


先ほどと全く同じ条件下で、あなたはAの扉を選択しました。
その直後に地震が起きて、偶然Cの扉が開いてしまいハズレである事が解ってしまいました。
しかし、司会者はもともとCの扉を開こうと決めていたので、ゲームを続行しました。
この時、あなたはBの扉へ変更するべきでしょうか?


ふむ、確かに前の問題との同一性が強調されて難しくなってますね。
っていうか、この問題って検算してないけど合ってるのかな・・・?

P.S
検算しました、答えは「変えても、変えなくても一緒」です。

はてな民に確率の問題を出してみよう

こんにちは、今回は確率の話です。


以前、職場で余興として問題を出したのですが、ほぼ全員がこの問題を知りませんでした。
理系が多く集まる職場なので、意外にみんな知らないんだなぁと思ったのですが、今度はリテラシーの高い(と勝手に思っている)はてな民に問題を出したら、どうなるんだろうと純粋な好奇心が沸いてきました。
なお有名な問題ですので、答えを知っている方はあまりヒントを出さない方向で・・・


問1

ティムはテレビのクイズ番組に出演し見事優勝をはたしました、優勝賞品の自動車をゲットするチャンスを得たのです。
司会者は言いました。


「ここにA、B、Cの3つのドアがあります。
1つのドアの後ろには自動車、それ以外の2つのドアの後ろにはヤギがいます。
ティムは1つのドアを選び、そのドアの中に自動車が入っていれば賞品をゲットできます。
もし、ヤギが入っていた場合はハズレです。
さぁティム、どのドアを選びますか?」


ティムは自動車がどうしても欲しかったので、本気で悩みました。
ティム「Aのドア!」
それを聞いた司会者はニヤリと笑いながら言いました。


「ここでティムにスペシャルヒントをあげましょう。
 ティムが選ばなかったBとCのドアですが・・・
 実はCのドアにはヤギが入っています!」


司会者はどのドアが当たりかを知っていたので、ティムの選んだドア以外で、ハズレのドアを1つ開けてヤギを見せました。
さらに司会者は言います。


「さぁ、ティム。
 特別に選んだドアを変更する権利をあげましょう。
 本当にAのドアでいいんですか?それとも変更しますか?」


観客は大盛り上がり!
なお、ゲームのルールは以下のとおり。

  1. 3つのドアに自動車、ヤギ、ヤギがランダムに入っている。
  2. ティムはドアを1つ選ぶ。
  3. ティムの選んだドア以外のドアのうち1つを必ず開ける。
  4. 司会者は自動車のあるドアを知っていて、必ずヤギの入っているドアを開ける。


ティムは最初の「A」のドアを貫くべきか、それともドアを変更すべきなのか・・・
どちらが自動車をゲットできる確率が高いでしょうか?
以下の中からお選びください。


A.最初の直感を信じるぞ!選択を変えない。
B.やっぱり選びなおそう!選択を変える。
C.惑わされるな!どちらも同じ確率だ!

問2

さらに別バージョンの問題もあります。
こちらも合わせてどうぞ。


問1のゲームルールで、司会者がハズレのドアを宣言しようとした瞬間、地震がおきました。
偶然にハズレのドアが開いてしまい、中のヤギが見えてしまいました。
しかし、司会者はもともとハズレのドアを開けようとしていたので、気にせずに進行を続けました。
なお、ゲームのルールは以下のとおりです。

  1. 地震によってランダムで1つのドアが必ず開く。
  2. 自動車のドア、またはティムが選んだドアが開いてしまった場合、ゲームは無効。


ティムは選択を変えた方がいいのでしょうか?
こちらも、以下の中から回答を選んでください。


A.最初の直感を信じるぞ!選択を変えない。
B.やっぱり選びなおそう!選択を変える。
C.惑わされるな!どちらも同じ確率だ!


有名な問題なんで、知っている人は知ってるだろうなぁ・・・
でも、どんな結果になるのかなぁ、オラなんかワクワクしてきたぞ!
って、これで誰も答えなかったらどうしよう・・・そしたら
「我はメシア、明日この世界を粛清する。」ってエントリー立てます。


解答編:【回答編】はてな民に確率の問題を出してみよう - Pashango’s Blog


追記:
2009/8/2, 2009/8/4
問題文に誤解を与える箇所があったので、文章を変更しました。
もし、考えてしまった人がいたら、ごめんなさい。

anhedoniaさんの指摘により、誤解を招く司会者の一言を変更しました。問題の本質は変わりません。ありがとうございました。