暫定、完成版
(未完成)やってみた。麻雀の上がりチェックプログラム - ラシウラから、ちょっと修正して要件を満たしたものです。
# -*- 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)