暫定、完成版

(未完成)やってみた。麻雀の上がりチェックプログラム - ラシウラから、ちょっと修正して要件を満たしたものです。

# -*- coding: utf-8 -*-

class Hai(object):
    def __init__(self, num, tsumo=False):
        self.num = num
        self.tsumo = tsumo
        pass
    def __eq__(self, other):
        return self.num == other.num and self.tsumo == other.tsumo
    pass

class Tenpai(object):
    def __init__(self, toitsu, sumentsu):
        self.groups = [toitsu] + sorted(sumentsu, self.rimentsu)
        pass
    def __hash__(self):
        return hash(str(self.group))
    def __eq__(self, other):
        return self.groups == other.groups
    def rimentsu(self, mentsua, mentsub):
        if is_juntsu(mentsua): return 1
        if is_juntsu(mentsub): return -1
        return 1
    def format(self):
        return "".join(self.format_group(group) for group in self.groups)
    def format_group(self, group):
        if any(hai.tsumo for hai in group):
            return "[%s]" % "".join(str(hai.num) for hai in group if not hai.tsumo)
        else:
            return "(%s)" % "".join(str(hai.num) for hai in group)
        pass
    pass

def main():
    while True:
        try:
            tehai_str = raw_input("haipai> ").strip()
            if not tehai_str: break
            tehai = parse(tehai_str)
            for tenpai in iter_tenpai(tehai):
                print(tenpai)
                pass
            pass
        except:
            import traceback
            traceback.print_exc()
            pass
        pass
    pass

def parse(tehai):
    if len(tehai) != 13: raise Exception("tehai should be 13 hai")
    return [Hai(int(tehai[i])) for i in range(len(tehai))]

def iter_tenpai(tehai):
    tsumo_list = [Hai(i, True) for i in range(1, 10)]
    # TBD: check: count of same num <= 4
    tenpaiset = set()
    for tsumo in tsumo_list:
        tsumotehai = rihai(tehai + [tsumo])
        for toitsu, rest in iter_toitsu(tsumotehai):
            for sumentsu in iter_allmentsu(rest):
                tenpai = Tenpai(toitsu, sumentsu).format()
                if tenpai in tenpaiset: continue
                tenpaiset.add(tenpai)
                yield tenpai
                pass
            pass
        pass
    pass

def rihai(tsumo_and_tehai):
    return sorted(tsumo_and_tehai, lambda a, b: cmp(a.num, b.num))

def iter_toitsu(ritsumo):
    same = []
    for i in range(len(ritsumo) - 1):
        if ritsumo[i].num == ritsumo[i+1].num:
            toitsu = ritsumo[i:i+2]
            rest = ritsumo[:i] + ritsumo[i+2:]
            if toitsu in same: continue
            same.append(toitsu)
            yield (toitsu, rest)
            pass
        pass
    pass

def is_juntsu(mentsu):
    return (mentsu[0].num + 1 == mentsu[1].num and 
            mentsu[0].num + 2 == mentsu[2].num)
def is_kotsu(mentsu):
    return mentsu[0].num == mentsu[1].num == mentsu[2].num
def is_mentsu(mentsu):
    return is_juntsu(mentsu) or is_kotsu(mentsu)

def pick1(hailist):
    for i in range(len(hailist)):
        yield (hailist[i], hailist[:i] + hailist[i+1:])
        pass
    pass

def iter_allmentsu(rest):
    if len(rest) == 0:
        yield []
        return
    a = rest[0]
    r1 = rest[1:]
    same = []
    for b, r2 in pick1(r1):
        for c, r3 in pick1(r2):
            mentsu = [a, b, c]
            if mentsu in same: continue
            same.append(mentsu)
            if not is_mentsu(mentsu): continue
            for allmentsu in iter_allmentsu(r3):
                yield [mentsu] + allmentsu
                pass
            pass
        pass
    pass

if __name__ == "__main__": main()

追記: ドメイン知識を利用する

上の四面子の探し方(iter_allmentsu)は、先頭の牌に対し全ての三牌の組を列挙してそれが順子か刻子かを判定していく方式をとってます。これは細かい性質を知らなくても問題文からすぐ割り出せるやり方です。

しかし麻雀のルールから考えれば、先頭の牌に対し刻子となる組み合わせ、順子となる組み合わせを探していく方が、効率が良いと思います。なぜなら探索範囲の限定がしやすいから。整牌済なら、刻子なら数値が違う牌が出た時点で失敗となり、順子なら一つ上二つ上の牌それぞれがない時点で失敗とわかるからです。それが下記のコードです:

def iter_allmentsu(hai_list):
    if len(hai_list) == 0:
        yield []
        return
    juntsu, rest = find_juntsu(hai_list)
    if juntsu:
        for allmentsu in iter_allmentsu(rest):
            yield [juntsu] + allmentsu
            pass
        pass
    kotsu, rest = find_kotsu(hai_list)
    if kotsu:
        for allmentsu in iter_allmentsu(rest):
            yield [kotsu] + allmentsu
            pass
        pass
    pass

def find_kotsu(hai_list):
    if (hai_list[0].num == hai_list[1].num and 
        hai_list[0].num == hai_list[2].num):
        return hai_list[:3], hai_list[3:]
    return [], hai_list

def find_juntsu(hai_list):
    juntsu = [hai_list[0], None, None]
    rest = []
    for hai in hai_list[1:]:
        if juntsu[1] is None and hai.num == juntsu[0].num + 1: 
            juntsu[1] = hai
            pass
        elif juntsu[2] is None and hai.num == juntsu[0].num + 2: 
            juntsu[2] = hai
            pass
        else: 
            rest.append(hai)
            pass
        pass
    return (juntsu, rest) if juntsu[1] and juntsu[2] else ([], hai_list)