【Python版】4つの4で1から10の数値を求める

@mogamoga1337 さんの 「[4つの4で1から10の数値を求める。](https://qiita.com/mogamoga1337/items/d6b2d2621bafc51f86b7)」 を Python で挑戦してみました。
全パターンを求めるようにしてみました。
ジェネレータ関数にして、見つかった式を次々と返すようにしています。
おまけで、1, 2, 3, 4 の組合せで 10 になる式を探してみました。

```py
def find(numbers, target):
    """
    numbers要素の間に演算子を挿入し、演算結果がtargetになる式を列挙する
    """
    for exp in combination(numbers):
        try:
            if eval(exp) == target:
                yield exp
        except (SyntaxError, TypeError, ZeroDivisionError):
            pass


def combination(selection):
    """
    selection要素と演算子の組合せを列挙する
    """
    if len(selection) <= 1:
        yield selection[0]
    elif len(selection) == 2:
        a, b = selection
        yield from make_exp(a, b)
        if a != b:
            yield from make_exp(b, a)
    else:
        for split in range(1, len(selection)):
            for a in combination(selection[:-split]):
                for b in combination(selection[-split:]):
                    yield from combination((a, b))


def make_exp(a, b):
    """
    aと演算子とbを使った式を列挙する。演算優先順位の優劣がある場合は括弧も使う。
    """
    yield f'{a}{b}'
    for op in '+-*/':
        yield f'{a}{op}{b}'
        if op in '*/':
            if low_priority(a):
                yield f'({a}){op}{b}'
            if low_priority(b):
                yield f'{a}{op}({b})'
            if low_priority(a) and low_priority(b):
                yield f'({a}){op}({b})'


def low_priority(exp, op='+-'):
    """
    expの中に優先順位が低いop演算子があればTrue。括弧の中の演算子は除外する
    """
    if not isinstance(exp, str):
        return False
    nest = 0
    for c in exp:
        if nest == 0 and c in op:
            return True
        if c == '(':
            nest += 1
        elif c == ')':
            nest -= 1
    return False


ORDER_NUMBER = str.maketrans('', '', '+-*/()')
ORDER_OP = str.maketrans('+-*/', 'ABCD', '0123456789()')


def uniq(found, sort=True):
    """
    重複要素を排除する。sort指示があれば数字順、演算子順にソートする。
    """
    unify = set(found)
    if sort:
        unify = sorted(unify, key=lambda e: e.translate(ORDER_OP))
        unify = sorted(unify, key=lambda e: (e.translate(ORDER_NUMBER), '(' in e))
    return unify


def main():
    """
    4つの4と演算子の組合せで1から10の値になる式を求める
    """
    for target in range(1, 11):
        print(f'{target}:', *uniq(find('4444', target)))
    print('おまけ')
    print('10:', *uniq(find('1234', 10)))
    print('100:', next(find('123456789', 100)))
    print('810:', next(find((1, 1, 4, 5, 1, 4), 810)))

if __name__ == '__main__':
    main()
```

```text:実行結果
1: 4+4/4-4 4-4+4/4 4*4/4/4 44/44 4/4+4-4 4/4-4+4 4/4*4/4 4/4/4*4 (4+4-4)/4 (4+4)/(4+4) (4-4+4)/4 4/(4+4-4) 4/(4-4+4)
2: 4/4+4/4 4-(4+4)/4 4*4/(4+4) 4/(4+4)*4
3: (4+4+4)/4 (4*4-4)/4
4: 4+(4-4)*4 4+(4-4)/4 4+4*(4-4) 4-(4-4)*4 4-(4-4)/4 (4-4)*4+4 4-4*(4-4) (4-4)/4+4 4*(4-4)+4
5: (4+4*4)/4 (4*4+4)/4
6: 4+(4+4)/4 (4+4)/4+4
7: 4+4-4/4 4-4/4+4 44/4-4
8: 4+4+4-4 4+4-4+4 4+4*4/4 4+4/4*4 4-4+4+4 4*4-4-4 4*4/4+4 4/4*4+4 (4+4)*4/4 (4+4)/4*4 4*(4+4)/4 4/4*(4+4)
9: 4+4+4/4 4+4/4+4 4/4+4+4
10: (44-4)/4
おまけ
10: 1+2+3+4 1*2*3+4 1*(2*3+4) 1+2+4+3 1+3+2+4 1*3*2+4 (1+3/2)*4 1*(3*2+4) 1+3+4+2 1*3*4-2 1*(3*4-2) 1+4+2+3 1*4+2*3 14/2+3 1*(4+2*3) 1+4+3+2 1*4+3*2 1*4*3-2 1*(4+3*2) 1*(4*3-2) 2+1+3+4 2*1*3+4 2/1*3+4 2+1+4+3 2+3+1+4 2*3+1*4 2*3*1+4 2*3/1+4 2+3+4+1 2*3+4*1 2*3+4/1 (2*3+4)*1 (2*3+4)/1 2+4+3+1 2*4+3-1 3+1+2+4 3-1+2*4 3*1*2+4 3/1*2+4 (3-1/2)*4 3+2+1+4 3*2+1*4 3*2*1+4 3*2/1+4 (3/2+1)*4 3+2+4+1 3+2*4-1 3*2+4*1 3*2+4/1 (3*2+4)*1 (3*2+4)/1 3+4+1+2 3*4-1*2 3*4*1-2 3*4/1-2 3+4+2+1 3+4*2-1 3*4-2*1 3*4-2/1 (3*4-2)*1 (3*4-2)/1 4+1+2+3 4+1*2*3 4*1+2*3 4/1+2*3 4+1+3+2 4+1*3*2 4*1+3*2 4*1*3-2 4/1+3*2 4/1*3-2 4*(1+3/2) 4+2+1+3 4+2*1*3 4+2/1*3 4*2-1+3 4+2+3+1 4+2*3*1 4+2*3/1 4*2+3-1 (4+2*3)*1 (4+2*3)/1 4+3+1+2 4+3*1*2 4+3/1*2 4*3-1*2 4*3*1-2 4*3/1-2 4*(3-1/2) 4+3+2+1 4+3*2*1 4+3*2/1 4*3-2*1 4*3-2/1 (4+3*2)*1 (4+3*2)/1 (4*3-2)*1 (4*3-2)/1 4*(3/2+1)
100: (1234-5*6)/7-8*9
810: 1*(11+4)*54
```