私のスキルで飯は食えるか?


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

Pythonで解きました。所要時間は1時間ぐらいだと思います(時間を測るのを忘れました)。

#encoding:shift-jis
from __future__ import division, print_function
__metaclass__ = type 
import re
from itertools import permutations, combinations

def removed(alist, x):
    alist = list(alist)
    alist.remove(x)
    return alist

def combinations2(iterable, r):
    """
    combinations2('ABCD', 2) --> (AB,CD) (AC,BD) (AD,BC) (BC,AD) (BD,AC) (AB,CD)
    >>> from itertools import combinations
    >>> i2 = s.combinations2("ABCDE", 2)
    >>> i1 = s.combinations("ABCDE", 2)
    >>> all(x == y for x, (y, _) in zip(i1, i2))
    True
    """
    pool = tuple(iterable)
    n = len(pool)
    for indices in permutations(range(n), r):
        if sorted(indices) == list(indices):
            comb = tuple(pool[i] for i in indices)
            rest = tuple(pool[i] for i in xrange(n) if i not in indices)
            yield comb, rest
    
def is_pair(tiles):
    assert len(tiles) == 2
    return len(set(tiles)) == 1

def is_triple(tiles):
    assert len(tiles) == 3
    return len(set(tiles)) == 1

def is_seq(tiles):
    assert len(tiles) == 3
    tiles = sorted(tiles)
    return tiles[0] == (tiles[1] - 1) == (tiles[2] - 2)
    
def pop_body(tiles):
    """
    パイの中から、『体』になりうる3枚組を全て見つけ、
    3枚組みとそれ以外を返します。
    """
    for comb, rest in combinations2(tiles, 3):
        if is_triple(comb) or is_seq(comb):
            yield comb, rest

def pop_head(tiles):
    """
    パイの中から、『頭』になりうる2枚組を全て見つけ、
    2枚組みとそれ以外を返します。
    """
    for comb, rest in combinations2(tiles, 2):
        if is_pair(comb):
            yield comb, rest

def stuple(iterable, *a, **kw):
    return tuple(sorted(iterable, *a, **kw))

def split_tiles_3(tiles):
    """
    13 - 1 = 12 枚のパイを 3, 3, 3, 3 の体4つに分解する仕方を返します
    """
    bodies = set()
    add = bodies.add
    for t1, r1 in pop_body(tiles):
        for t2, r2 in pop_body(r1):
            for t3, r3 in pop_body(r2):
                for t4, r4 in pop_body(r3):
                    add(stuple([t1, t2, t3, t4]))
    return bodies

def split_tiles_2_3(tiles):
    """
    13 - 2 = 11 枚のパイを 2, 3, 3, 3 の頭1つと体4つに分解する仕方を返します
    """
    bodies = set()
    add = bodies.add
    for t1, r1 in pop_head(tiles):
        for t2, r2 in pop_body(r1):
            for t3, r3 in pop_body(r2):
                for t4, r4 in pop_body(r3):
                    add(stuple([t1, t2, t3, t4]))
    return bodies

def solv_1(tiles):
    """
    13枚の牌から「待ちの部分」が1枚になる、待ちを探します。
    """
    tiles = sorted(int(t) for t in tiles)
    for t in set(tiles):
        tiles1 = removed(tiles, t)
        for body in split_tiles_3(tiles1):
            yield (body, (t,))

def is_wait2(t):
    """
    「待ちの部分」になりうる2枚か判定します。
    すなわち、アンコかシュンツを作りうるか判定します。
    """
    assert len(t) == 2
    return (t[0] - t[1]) in [-2, -1, 0, 1, 2]

def solv_2(tiles):
    """
    13枚の牌から「待ちの部分」が2枚になる、待ちを探します。
    """
    tiles = sorted(int(t) for t in tiles)
    for t in set(combinations(tiles, 2)):
        if not is_wait2(t):
            continue
        tiles1 = removed(removed(tiles, t[0]), t[1])
        for body in split_tiles_2_3(tiles1):
            yield (body, t)

def solv(tiles):
    """すべての待ちをタプルで返す"""
    tiles = [int(t) for t in tiles]
    
    answers = []
    answers.extend(solv_1(tiles))
    answers.extend(solv_2(tiles))
    return answers

def solv_s(s):
    """回答を文字列で返す"""
    return [ans_to_str(ans) for ans in solv(s)]

def ans_to_str(ans):
    body, w = ans
    body = ["".join(map(str, t)) for t in body]
    w = "".join(map(str, w))
    return "({0[0]})({0[1]})({0[2]})({0[3]})[{1}]".format(body, w)

def str_to_ans(s):
    m = re.match("\((\d{2,3})\)\((\d{2,3})\)\((\d{2,3})\)\((\d{2,3})\)\[(\d{1,2})\]", s)
    bodies = stuple(stuple(int(d) for d in m.group(i)) for i in [1, 2,3,4])
    wait = stuple(int(d) for d in m.group(5))
    return (bodies, wait)

def answer_eq(ans1, ans2):
    return stuple(str_to_ans(ans) for ans in ans1) == stuple(str_to_ans(ans) for ans in ans2)

def test():
    assert answer_eq(solv_s("1112224588899"), ["(111)(222)(888)(99)[45]"])
    
    assert answer_eq(solv_s("1122335556799"), [
        "(123)(123)(555)(99)[67]",
        "(123)(123)(55)(567)[99]",
        "(123)(123)(99)(567)[55]",
    ])
    
    assert answer_eq(solv_s("1112223335559"), [
        "(123)(123)(123)(555)[9]",
        "(111)(222)(333)(555)[9]",
    ])
    
    assert len(solv_s("1223344888999")) == 3
    
    from pprint import pprint
    #九蓮宝燈は、すべての待ちを数えられなかったので、出力のみ
    pprint((solv_s("1112345678999")))
    
    
def main():
    test()

if "__main__" == __name__:
    main()

パソコンが修理中だったので、Pythonは久しぶりだったのですが、
一時HaskellClojureの勉強をしたせいか、
listを非破壊的に操作する関数が無いことが窮屈に感じました。

上のコードで言えば、removedのような関数が標準で用意されていて欲しいです。

並行処理うんぬんのような、難しい意味では無くて、
単にリストを複製する必要がなくなり、コードが短くなるという意味ですが。