LoginSignup
7
4

More than 5 years have passed since last update.

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

Posted at

前書き

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

コードと動作

コードは次の通り。素因数を表示するものだ。
一応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のクロージャで末尾再帰最適化をする。
さすがにこれも込みでワンライナーにするのは骨だ。

7
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4