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

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

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


面白そうなので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]

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