Python
ラムダ式
ワンライナー
クソコード

素因数を出力するワンライナーを書いてみた

前書き

あるときふと思った。クソコードを書きたい。
この記事は、細々とやっているブログからの転載です。

コードと動作

コードは次の通り。素因数を表示するものだ。
一応Python3だが、print関数をprint文に置き換えればPython2でも動作する。

print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))

実行とその結果は次のようである。

>python -c "print((lambda f: (lambda n: f(f, [], n, 2)))(lambda f, r, n, i: r if n == 1 else (r.append(i) or f(f, r, n/i, i)) if n % i == 0 else f(f, r, n, i+1))(int(input())))"     
100
[2, 2, 5, 5]   

我ながら満足のクソっぷりである。
改善したい点を挙げるとしたら、リストを使いまわしているところだろうか。
ラムダ式を上手くジェネレータのように活用できないだろうか。

一応解説

解説というか、見やすく分解しただけだが。

print(
    # 再帰可能にする工夫と、高階関数としての役割
    (lambda func: 
        (lambda num:
            func(func, [], num, 2)
        )
    )
    # 素因数分解をつかさどる部分
    (lambda func, result, num, i:
        # 終了条件と返り値
        result if num == 1
        # i が num の約数のとき 
        else (result.append(i) or func(func, result, num/i, i)) if num % i == 0    
        # i が num の約数でないとき
        else                      func(func, result, num, i+1)
    )
    # 実際に関数に与える引数
    (int(input()))
)  

ワンラインの拘りなしにこれを書くとこのようになる。

def higher_order(func):
    return lambda num: func(func, [], num, 2)    

@higher_order
def ret_factor(func, result, num, i):
    if num == 1:
        return result

    if num % i == 0:
        result.append(i)
        return func(func, result, num/i, i)
    else:
        return func(func, result, num, i+1)

print(
    ret_factor(int(input()))
)

そもそもラムダ式がわからん。デコレータも知らん。という方はこちら。

def ret_factor(func, result, num, i):
    if num == 1:
        return result

    if num % i == 0:
        result.append(i)
        return func(func, result, num/i, i)
    else:
        return func(func, result, num, i+1)

print(
    ret_factor(func=ret_factor, result=[], num=int(input()), i=2)    
)

普通に書くなら

ラムダ式での再帰のために、第一引数に自身を取る冗長な構造にしている。
Pythonでのワンライナーが非常に参考になった。

普通に書くなら、次のようになる。

def ret_factor(result, num, i):
    if num == 1:
        return result

    if num % i == 0:
        result.append(i)
        return ret_factor(result, num/i, i)    
    else:
        return ret_factor(result, num, i+1)

また、リストに値を格納して最後に返すのは、大抵無駄だ。
このようなときには、ジェネレータを使うべきである。

def ret_factor(num, i):
    if num == 1:
        return

    if num % i == 0:
        yield i
        yield from ret_factor(num/i, i)
    else:
        yield from ret_factor(num, i+1)    

本当はこれをワンラインにしたいのだが...
いかんせん終了時の処理を上手く表現できないのだ。

その後の足掻き

print(
    list(
        # 再帰可能にする工夫と、高階関数としての役割
        (lambda func: 
            (lambda num:
                func(func, num, 2)
            )
        )
        # 素因数分解をつかさどる部分
        (lambda func, num, i:
            # 終了条件
            None if num == 1
            # i が num の約数のとき 
            else ((yield i), (yield from func(func, num/i, i))) if num % i == 0    
            # i が num の約数でないとき
            else             (yield from func(func, num, i+1))
        )
        # 実際に関数に与える引数
        (int(input()))
    )
)

このようにしたら出来た。達成感より先に気持ち悪さがあるが。
これはつまり、次のように書けるということなのだ...

>>> def func(num):
...     ((yield num), (yield from func(num-1))) if num > 0 else None    
...
>>> list(func(10))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

なんとも恐ろしい。

さらに改造

例えば、次のように改造できる。

print(
    (lambda max: [m for m in range(2, max) if
        # 約数(1を除く)が1つだったら素数
        len(
            # 再帰可能にする工夫と、高階関数としての役割
            (lambda func: 
                (lambda num:
                    func(func, [], num, 2)
                )
            )
            # 素因数分解をつかさどる部分
            (lambda func, result, num, i:
                # 終了条件と返り値
                result if num == 1
                # i が num の約数のとき 
                else (result.append(i) or func(func, result, num/i, i)) if num % i == 0    
                # i が num の約数でないとき
                else                      func(func, result, num, i+1)
            )
            # 実際に関数に与える引数
            (m)
        ) == 1]
    )
    # 求める素数の上限
    (int(input()))
)

改行と無駄な空白を潰すとこんな感じ。

print((lambda max: [m for m in range(2, max) if len((lambda func: (lambda num: func(func, [], num, 2)))(lambda func, result, num, i: result if num == 1 else (result.append(i) or func(func, result, num/i, i)) if num % i == 0 else func(func, result, num, i+1))(m)) == 1])(int(input())))

入力された数値未満の素数リストを出力するワンライナーだ。
しかし、1000ほどの大きな値を入れるとRecursionErrorで落ちる。
末尾再帰最適化で解消できるはずだが、まあそこまではしなくてよいだろう。

一応参考までに。Pythonのクロージャで末尾再帰最適化をする。
さすがにこれも込みでワンライナーにするのは骨だ。